@sulala/agent 0.1.13 β 0.1.14
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 +3 -3
- package/dashboard/dist/assets/index-DegBJNv6.css +1 -0
- package/dashboard/dist/assets/index-pVHpAj3h.js +83 -0
- package/dashboard/dist/index.html +2 -2
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +21 -6
- package/dist/agent/loop.js.map +1 -1
- package/dist/agent/skill-generate.d.ts +1 -1
- package/dist/agent/skill-generate.d.ts.map +1 -1
- package/dist/agent/skill-generate.js +10 -3
- package/dist/agent/skill-generate.js.map +1 -1
- package/dist/agent/skill-install.d.ts +12 -1
- package/dist/agent/skill-install.d.ts.map +1 -1
- package/dist/agent/skill-install.js +130 -15
- package/dist/agent/skill-install.js.map +1 -1
- package/dist/agent/skills.d.ts +4 -3
- package/dist/agent/skills.d.ts.map +1 -1
- package/dist/agent/skills.js +53 -25
- package/dist/agent/skills.js.map +1 -1
- package/dist/agent/tool/spec-loader.d.ts +7 -0
- package/dist/agent/tool/spec-loader.d.ts.map +1 -0
- package/dist/agent/tool/spec-loader.js +540 -0
- package/dist/agent/tool/spec-loader.js.map +1 -0
- package/dist/agent/tools.d.ts.map +1 -1
- package/dist/agent/tools.integrations.test.js +4 -5
- package/dist/agent/tools.integrations.test.js.map +1 -1
- package/dist/agent/tools.js +144 -367
- package/dist/agent/tools.js.map +1 -1
- package/dist/ai/orchestrator.d.ts.map +1 -1
- package/dist/ai/orchestrator.js +82 -17
- package/dist/ai/orchestrator.js.map +1 -1
- package/dist/cli.d.ts +4 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +16 -8
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +20 -5
- package/dist/config.js.map +1 -1
- package/dist/db/index.d.ts +14 -7
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +108 -30
- package/dist/db/index.js.map +1 -1
- package/dist/gateway/server.d.ts.map +1 -1
- package/dist/gateway/server.js +141 -15
- package/dist/gateway/server.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/onboard-env.d.ts +1 -1
- package/dist/onboard-env.d.ts.map +1 -1
- package/dist/onboard-env.js +2 -0
- package/dist/onboard-env.js.map +1 -1
- package/dist/types.d.ts +5 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/watcher/index.d.ts.map +1 -1
- package/dist/watcher/index.js +1 -2
- package/dist/watcher/index.js.map +1 -1
- package/package.json +4 -5
- package/src/index.ts +1 -1
- package/context/00-rules.md +0 -1
- package/context/airtable.md +0 -35
- package/context/apple-notes.md +0 -99
- package/context/asana.md +0 -37
- package/context/bluesky.md +0 -46
- package/context/calendar.md +0 -63
- package/context/country-info.md +0 -13
- package/context/create-skill.md +0 -128
- package/context/discord.md +0 -30
- package/context/docs.md +0 -29
- package/context/drive.md +0 -49
- package/context/dropbox.md +0 -39
- package/context/facebook.md +0 -47
- package/context/fetch-form-api.md +0 -16
- package/context/figma.md +0 -30
- package/context/files.md +0 -30
- package/context/git.md +0 -37
- package/context/github.md +0 -58
- package/context/gmail.md +0 -52
- package/context/google.md +0 -28
- package/context/hellohub.md +0 -29
- package/context/jira.md +0 -46
- package/context/linear.md +0 -40
- package/context/news.md +0 -64
- package/context/notion.md +0 -45
- package/context/portal-integrations.md +0 -42
- package/context/post-to-x.md +0 -50
- package/context/sheets.md +0 -47
- package/context/slack.md +0 -48
- package/context/slides.md +0 -35
- package/context/stripe.md +0 -38
- package/context/tes.md +0 -7
- package/context/test.md +0 -7
- package/context/weather.md +0 -32
- package/context/zoom.md +0 -28
- package/dashboard/dist/assets/index-BTx-9jCj.css +0 -1
- package/dashboard/dist/assets/index-B_QGQ8c_.js +0 -83
- package/registry/apple-notes.md +0 -99
- package/registry/bluesky.md +0 -34
- package/registry/files.md +0 -30
- package/registry/git.md +0 -37
- package/registry/news.md +0 -64
- package/registry/skills-registry.json +0 -46
- package/registry/weather.md +0 -32
package/context/dropbox.md
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: dropbox
|
|
3
|
-
description: Use Dropbox (files) via the Portal. When the user asks to list, upload, or download Dropbox files, list connections with list_integrations_connections (provider dropbox) and use run_command with curl to the Dropbox API or gateway.
|
|
4
|
-
metadata:
|
|
5
|
-
{
|
|
6
|
-
"sulala": {
|
|
7
|
-
"emoji": "π",
|
|
8
|
-
"requires": { "bins": ["curl"] }
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
# Dropbox
|
|
14
|
-
|
|
15
|
-
1. **list_integrations_connections** with `provider: "dropbox"` β get `connection_id`.
|
|
16
|
-
2. **get_connection_token** with that `connection_id` β returns `accessToken` (do not curl the portal).
|
|
17
|
-
3. **run_command (curl)** with `Authorization: Bearer <accessToken>`. RPC-style endpoints use `Content-Type: application/json` and body; content endpoints use `Dropbox-API-Arg` header.
|
|
18
|
-
|
|
19
|
-
Add `api.dropboxapi.com` and `content.dropboxapi.com` to **ALLOWED_CURL_HOSTS**. Official docs: https://www.dropbox.com/developers/documentation/http/documentation
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## List folder
|
|
24
|
-
|
|
25
|
-
- **List folder**: `POST https://api.dropboxapi.com/2/files/list_folder` with body `{"path": ""}` for root or `{"path": "/FolderName"}`. Returns `entries[].name`, `entries[].id`, `entries[].tag` (file/folder). Pagination: `cursor` in response, then `POST https://api.dropboxapi.com/2/files/list_folder/continue` with `{"cursor": "..."}`.
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## Download file
|
|
30
|
-
|
|
31
|
-
- **Download**: `POST https://content.dropboxapi.com/2/files/download` with header `Dropbox-API-Arg: {"path": "/path/to/file.txt"}`. No body. Response body is file content (binary). Use same `Authorization: Bearer <token>`.
|
|
32
|
-
|
|
33
|
-
---
|
|
34
|
-
|
|
35
|
-
## Upload file
|
|
36
|
-
|
|
37
|
-
- **Upload** (small file): `POST https://content.dropboxapi.com/2/files/upload` with header `Dropbox-API-Arg: {"path": "/path/to/destination.txt", "mode": "add"}` and `Content-Type: application/octet-stream`, body = raw file bytes. `mode`: "add" (always add), "overwrite", "add" with autorename.
|
|
38
|
-
|
|
39
|
-
Requirements: **PORTAL_GATEWAY_URL**, **PORTAL_API_KEY**; user must connect Dropbox in the Portal.
|
package/context/facebook.md
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: facebook
|
|
3
|
-
description: Use Facebook (Graph API, pages) via the Portal. When the user asks to post or manage Facebook content, list connections with list_integrations_connections (provider facebook) and use run_command with curl to the Graph API or gateway.
|
|
4
|
-
metadata:
|
|
5
|
-
{
|
|
6
|
-
"sulala": {
|
|
7
|
-
"emoji": "π",
|
|
8
|
-
"requires": { "bins": ["curl"] }
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
# Facebook
|
|
14
|
-
|
|
15
|
-
1. **list_integrations_connections** with `provider: "facebook"` β get `connection_id`.
|
|
16
|
-
2. **get_connection_token** with that `connection_id` β returns `accessToken` (do not curl the portal).
|
|
17
|
-
3. **run_command (curl)** to `https://graph.facebook.com`. Use `access_token=<accessToken>` in query or `Authorization: Bearer <accessToken>` in header per endpoint.
|
|
18
|
-
|
|
19
|
-
Add `graph.facebook.com` to **ALLOWED_CURL_HOSTS**. Official docs: https://developers.facebook.com/docs/graph-api
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## User and pages
|
|
24
|
-
|
|
25
|
-
- **Get user info**: `GET https://graph.facebook.com/me?fields=id,name,email&access_token=<token>`. Do **not** use this id as the target for posting (it is a person, not a page).
|
|
26
|
-
- **Get user's pages** (required before any post): `GET https://graph.facebook.com/me/accounts?access_token=<token>`. Returns `data[].id` (page id), `data[].name`, `data[].access_token` (page token). Use these for posting.
|
|
27
|
-
|
|
28
|
-
---
|
|
29
|
-
|
|
30
|
-
## Post to page (required flow)
|
|
31
|
-
|
|
32
|
-
**Never post to the user's ID or /me.** The user ID from `GET /me` is a person, not a page. You must use a **page** id and **page** access token from `me/accounts`. **Never use placeholders** like `your_page_id` or `<page_id>` in real API callsβFacebook will reject them. Always get real values from step 2.
|
|
33
|
-
|
|
34
|
-
1. Get **user** token: `list_integrations_connections` β `get_connection_token` β `accessToken`.
|
|
35
|
-
2. **Call** `GET https://graph.facebook.com/v25.0/me/accounts?access_token=<user_token>`. Response: `data[]` with `id`, `name`, `access_token` (page token). You must do this before posting; the POST needs real `id` and `access_token` from this response.
|
|
36
|
-
3. Pick a page: if the user said a name (e.g. "playoutdoor"), find the item in `data` whose `name` matches (e.g. "PlayOutdoor : Tennis, Bike & Run"); otherwise use `data[0]`. Let `PAGE_ID` = that itemβs `id`, `PAGE_TOKEN` = that itemβs `access_token`.
|
|
37
|
-
4. Post: `POST https://graph.facebook.com/v25.0/PAGE_ID/feed` with body `{"message": "Your text here", "access_token": "PAGE_TOKEN"}`. Use the actual string values for PAGE_ID and PAGE_TOKEN from step 3 (e.g. if id is `677659355438097`, the URL is `.../v25.0/677659355438097/feed`).
|
|
38
|
-
|
|
39
|
-
- **Photo**: `POST https://graph.facebook.com/v25.0/PAGE_ID/photos` with the **page** token. The image must be at a **publicly reachable URL** β Facebook's servers fetch it. **URLs like http://127.0.0.1 or http://localhost do not work** (Facebook cannot reach your machine). Use a public URL (e.g. deploy the gateway or expose it via ngrok and use that base URL for uploads). When the user attaches media in chat, the message includes "[Attached media ... URLs]"; use one of those URLs only if it is public. Use **form data** (not JSON) for the photos endpoint. Example with curl: `curl -X POST "https://graph.facebook.com/v25.0/PAGE_ID/photos" -F "url=IMAGE_PUBLIC_URL" -F "caption=Optional caption" -F "access_token=PAGE_TOKEN"`. Replace PAGE_ID, IMAGE_PUBLIC_URL, and PAGE_TOKEN with real values from step 3. For multiple images, post one at a time.
|
|
40
|
-
|
|
41
|
-
---
|
|
42
|
-
|
|
43
|
-
## Page insights (optional)
|
|
44
|
-
|
|
45
|
-
- **Page insights**: `GET https://graph.facebook.com/<page_id>/insights?metric=page_impressions&access_token=<page_token>`.
|
|
46
|
-
|
|
47
|
-
Requirements: **PORTAL_GATEWAY_URL**, **PORTAL_API_KEY**; user must connect Facebook in the Portal. For posting to a Page, the Facebook app must request (and the user grant) **pages_manage_posts** and **pages_read_engagement**; the token used for the POST must be the **page** access token from `me/accounts`, not the user token. If photo post fails: ensure the image URL is **public** (not 127.0.0.1/localhost), use form params (`-F`) for `/photos`, and use the page token from `me/accounts`.
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: fetch-form-api
|
|
3
|
-
description: Fetches and returns data from a free API. Use when the user asks for specific data from a public API.
|
|
4
|
-
---
|
|
5
|
-
# Fetch Form API
|
|
6
|
-
|
|
7
|
-
Use when the user asks for data from a free API, such as forms, weather, or any public information.
|
|
8
|
-
|
|
9
|
-
## How to use
|
|
10
|
-
- Invoke this skill to fetch data from available public APIs by specifying the endpoint and any necessary parameters.
|
|
11
|
-
|
|
12
|
-
## Example
|
|
13
|
-
- To fetch weather information, specify the weather API endpoint along with location parameters.
|
|
14
|
-
|
|
15
|
-
## Limits
|
|
16
|
-
- Ensure the API has a rate limit that does not exceed the allowed number of requests.
|
package/context/figma.md
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: figma
|
|
3
|
-
description: Use Figma (files, projects) via the Portal. When the user asks about Figma files or designs, list connections with list_integrations_connections (provider figma) and use run_command with curl to the Figma API or gateway.
|
|
4
|
-
metadata:
|
|
5
|
-
{
|
|
6
|
-
"sulala": {
|
|
7
|
-
"emoji": "π¨",
|
|
8
|
-
"requires": { "bins": ["curl"] }
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
# Figma
|
|
14
|
-
|
|
15
|
-
1. **list_integrations_connections** with `provider: "figma"` β get `connection_id`.
|
|
16
|
-
2. **get_connection_token** with that `connection_id` β returns `accessToken` (do not curl the portal).
|
|
17
|
-
3. **run_command (curl)** with `Authorization: Bearer <accessToken>` (or `X-Figma-Token` per Figma docs).
|
|
18
|
-
|
|
19
|
-
Base URL: `https://api.figma.com/v1`. Add `api.figma.com` to **ALLOWED_CURL_HOSTS**. Official docs: https://www.figma.com/developers/api
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## Files and projects
|
|
24
|
-
|
|
25
|
-
- **Get file** (document structure, nodes): `GET https://api.figma.com/v1/files/<file_key>`. Returns document tree, pages, frames. `file_key` is the ID from the Figma file URL.
|
|
26
|
-
- **Get file nodes** (specific nodes): `GET https://api.figma.com/v1/files/<file_key>/nodes?ids=<node_id1>,<node_id2>`.
|
|
27
|
-
- **Get projects** (team): `GET https://api.figma.com/v1/teams/<team_id>/projects`. Returns `projects[].id`, `name`. Get `team_id` from user or from project response.
|
|
28
|
-
- **Get project files**: `GET https://api.figma.com/v1/projects/<project_id>/files`. Returns `files[].key`, `name`.
|
|
29
|
-
|
|
30
|
-
Requirements: **PORTAL_GATEWAY_URL**, **PORTAL_API_KEY**; user must connect Figma in the Portal. File/project must be accessible to the connected account.
|
package/context/files.md
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: files
|
|
3
|
-
description: Basic file operations via run_command. Use when the user asks to list files, show file contents, search within files, or inspect directories.
|
|
4
|
-
metadata:
|
|
5
|
-
{
|
|
6
|
-
"sulala": {
|
|
7
|
-
"requires": { "bins": [] }
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
# File operations
|
|
13
|
-
|
|
14
|
-
Use **run_command** with common shell tools to read and inspect files. Add required binaries to ALLOWED_BINARIES (e.g. ls, cat, head, tail, wc, grep, find).
|
|
15
|
-
|
|
16
|
-
## List files
|
|
17
|
-
|
|
18
|
-
- `ls` β list directory contents
|
|
19
|
-
- `ls -la` β long format with hidden files
|
|
20
|
-
|
|
21
|
-
## Read files
|
|
22
|
-
|
|
23
|
-
- `cat path/to/file` β output entire file
|
|
24
|
-
- `head -n 20 path/to/file` β first 20 lines
|
|
25
|
-
- `tail -n 50 path/to/file` β last 50 lines
|
|
26
|
-
|
|
27
|
-
## Search
|
|
28
|
-
|
|
29
|
-
- `grep -r "pattern" path/` β search in files recursively
|
|
30
|
-
- `find path -name "*.md"` β find files by name
|
package/context/git.md
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: git
|
|
3
|
-
description: Basic Git operations via run_command. Use when the user asks to check status, diff, log, branch, or perform simple git commands.
|
|
4
|
-
metadata:
|
|
5
|
-
{
|
|
6
|
-
"sulala": {
|
|
7
|
-
"requires": { "bins": ["git"] }
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
# Git operations
|
|
13
|
-
|
|
14
|
-
Use **run_command** with `binary: "git"` and appropriate args. Add `git` to ALLOWED_BINARIES.
|
|
15
|
-
|
|
16
|
-
## Status and diff
|
|
17
|
-
|
|
18
|
-
- `git status`
|
|
19
|
-
- `git diff`
|
|
20
|
-
- `git diff --staged`
|
|
21
|
-
- `git log -n 10 --oneline`
|
|
22
|
-
|
|
23
|
-
## Branches
|
|
24
|
-
|
|
25
|
-
- `git branch`
|
|
26
|
-
- `git branch -a`
|
|
27
|
-
- `git checkout -b new-branch`
|
|
28
|
-
|
|
29
|
-
## Info
|
|
30
|
-
|
|
31
|
-
- `git show HEAD:path/to/file` β show file at HEAD
|
|
32
|
-
- `git rev-parse --abbrev-ref HEAD` β current branch name
|
|
33
|
-
|
|
34
|
-
## Limits
|
|
35
|
-
|
|
36
|
-
- Do not run destructive commands (reset --hard, push --force) without explicit user confirmation.
|
|
37
|
-
- Prefer read-only commands when the user only asks to inspect.
|
package/context/github.md
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: github
|
|
3
|
-
description: Use GitHub (repos, issues, PRs, comments) via the Portal. When the user asks about repos, issues, or pull requests, list connections with list_integrations_connections (provider github) and use run_command with curl to the GitHub API or gateway.
|
|
4
|
-
metadata:
|
|
5
|
-
{
|
|
6
|
-
"sulala": {
|
|
7
|
-
"emoji": "π",
|
|
8
|
-
"requires": { "bins": ["curl"] }
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
# GitHub
|
|
14
|
-
|
|
15
|
-
1. **list_integrations_connections** with `provider: "github"` β get `connection_id`.
|
|
16
|
-
2. **get_connection_token** with that `connection_id` β returns `accessToken` (do not curl the portal).
|
|
17
|
-
3. **run_command (curl)** with `Authorization: Bearer <accessToken>` and `Accept: application/vnd.github.v3+json` for all requests.
|
|
18
|
-
|
|
19
|
-
Base URL: `https://api.github.com`. Add `api.github.com` to **ALLOWED_CURL_HOSTS**.
|
|
20
|
-
|
|
21
|
-
Official docs: https://docs.github.com/en/rest
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## Repositories
|
|
26
|
-
|
|
27
|
-
- **List repos** (authenticated user): `GET https://api.github.com/user/repos?per_page=20&sort=updated`. Returns `[].id`, `name`, `full_name`, `clone_url`, `private`.
|
|
28
|
-
- **List repos** (org): `GET https://api.github.com/orgs/<org>/repos?per_page=20`.
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
## Issues
|
|
33
|
-
|
|
34
|
-
- **List issues** (repo): `GET https://api.github.com/repos/<owner>/<repo>/issues?state=all&per_page=20`. Returns `[].number`, `title`, `state`, `body`, `user.login`.
|
|
35
|
-
- **Create issue**: `POST https://api.github.com/repos/<owner>/<repo>/issues` with body `{"title": "Title", "body": "Description"}`. Optional: `"labels": ["bug"]`, `"assignees": ["username"]`.
|
|
36
|
-
- **Get issue**: `GET https://api.github.com/repos/<owner>/<repo>/issues/<issue_number>`.
|
|
37
|
-
|
|
38
|
-
---
|
|
39
|
-
|
|
40
|
-
## Comments
|
|
41
|
-
|
|
42
|
-
- **List comments** (on issue): `GET https://api.github.com/repos/<owner>/<repo>/issues/<issue_number>/comments`.
|
|
43
|
-
- **Create comment** (on issue): `POST https://api.github.com/repos/<owner>/<repo>/issues/<issue_number>/comments` with body `{"body": "Comment text"}`.
|
|
44
|
-
|
|
45
|
-
---
|
|
46
|
-
|
|
47
|
-
## Pull requests
|
|
48
|
-
|
|
49
|
-
- **List PRs**: `GET https://api.github.com/repos/<owner>/<repo>/pulls?state=open&per_page=20`.
|
|
50
|
-
- **Create PR**: `POST https://api.github.com/repos/<owner>/<repo>/pulls` with body `{"title": "Title", "head": "<branch>", "base": "main", "body": "Description"}`. `head` is the branch with changes (e.g. `username:feature-branch` for fork).
|
|
51
|
-
|
|
52
|
-
---
|
|
53
|
-
|
|
54
|
-
## File content
|
|
55
|
-
|
|
56
|
-
- **Get file**: `GET https://api.github.com/repos/<owner>/<repo>/contents/<path>`. Response has `content` (base64); decode to get file text. Optional: `?ref=<branch>`.
|
|
57
|
-
|
|
58
|
-
Requirements: **PORTAL_GATEWAY_URL**, **PORTAL_API_KEY**; user must connect GitHub in the Portal.
|
package/context/gmail.md
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: gmail
|
|
3
|
-
description: Use Gmail via the Portal. When the user asks to read email, send email, list messages, or archive mail, use this skill with list_integrations_connections (provider gmail) and run_command + curl.
|
|
4
|
-
metadata:
|
|
5
|
-
{
|
|
6
|
-
"sulala": {
|
|
7
|
-
"emoji": "π§",
|
|
8
|
-
"requires": { "bins": ["curl"] }
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
# Gmail
|
|
14
|
-
|
|
15
|
-
Use **list_integrations_connections** with `provider: "gmail"`, then **get_connection_token** to get an OAuth token (do not curl the portal from run_commandβuse the tool). Then call Gmail with that token.
|
|
16
|
-
|
|
17
|
-
**Required order:**
|
|
18
|
-
1. **list_integrations_connections** with `provider: "gmail"` β get `connection_id` (e.g. from `connections[0].id`).
|
|
19
|
-
2. **get_connection_token** with that `connection_id` β returns `accessToken`. This runs server-side; the agent does not curl the portal.
|
|
20
|
-
3. **run_command (curl)** β call Gmail APIs with header `Authorization: Bearer <accessToken>` (the value from step 2). Do **not** use the Portal API key on Gmail URLs.
|
|
21
|
-
|
|
22
|
-
If you get 401, you skipped step 2: call **get_connection_token** first, then use the returned `accessToken` in the Gmail curl.
|
|
23
|
-
|
|
24
|
-
Add `gmail.googleapis.com` to **ALLOWED_CURL_HOSTS**.
|
|
25
|
-
|
|
26
|
-
Base URL: `https://gmail.googleapis.com/gmail/v1`
|
|
27
|
-
|
|
28
|
-
---
|
|
29
|
-
|
|
30
|
-
## List messages (inbox)
|
|
31
|
-
|
|
32
|
-
`GET https://gmail.googleapis.com/gmail/v1/users/me/messages?maxResults=20` (optional: `q=is:unread`, `pageToken`). Returns `messages[].id`; use `threadId` if needed.
|
|
33
|
-
|
|
34
|
-
---
|
|
35
|
-
|
|
36
|
-
## Get message (body, subject, from)
|
|
37
|
-
|
|
38
|
-
`GET https://gmail.googleapis.com/gmail/v1/users/me/messages/<messageId>?format=full` (or `format=metadata`). Decode `payload.parts[].body.data` or `payload.body.data` (base64url) for body.
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
## Send email
|
|
43
|
-
|
|
44
|
-
Build MIME (From, To, Subject, body); base64url-encode it. `POST https://gmail.googleapis.com/gmail/v1/users/me/messages/send` with body `{"raw": "<base64url-encoded-mime>"}`. Or use `{"raw": "<base64url>"}` where the MIME string is encoded (e.g. with a small script or base64).
|
|
45
|
-
|
|
46
|
-
---
|
|
47
|
-
|
|
48
|
-
## Archive message
|
|
49
|
-
|
|
50
|
-
`POST https://gmail.googleapis.com/gmail/v1/users/me/messages/<messageId>/modify` with body `{"removeLabelIds": ["INBOX"]}`.
|
|
51
|
-
|
|
52
|
-
Requirements: **PORTAL_GATEWAY_URL**, **PORTAL_API_KEY**; user must connect Gmail in the Portal or dashboard Integrations.
|
package/context/google.md
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: google
|
|
3
|
-
description: Google services are split by product. Use the skill that matches the requestβcalendar for events, gmail for email, drive for files, docs/sheets/slides for Docs/Sheets/Slides. Do not loop over one doc; pick the right skill.
|
|
4
|
-
metadata:
|
|
5
|
-
{
|
|
6
|
-
"sulala": {
|
|
7
|
-
"emoji": "π·",
|
|
8
|
-
"requires": { "bins": ["curl"] }
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
# Google (per-service skills)
|
|
14
|
-
|
|
15
|
-
**Use one skill per product.** The Portal has separate providers; there is no single "google" provider.
|
|
16
|
-
|
|
17
|
-
| User intent | Skill to use | Provider for list_integrations_connections |
|
|
18
|
-
|--------------------------|----------------|--------------------------------------------|
|
|
19
|
-
| Create event, calendar | **calendar** | `"calendar"` |
|
|
20
|
-
| Email, Gmail | **gmail** | `"gmail"` |
|
|
21
|
-
| Drive files, folders | **drive** | `"drive"` |
|
|
22
|
-
| Google Docs | **docs** | `"docs"` |
|
|
23
|
-
| Google Sheets | **sheets** | `"sheets"` |
|
|
24
|
-
| Google Slides | **slides** | `"slides"` |
|
|
25
|
-
|
|
26
|
-
For "create an event at 9 PM" β use the **calendar** skill only. For "send an email" β use the **gmail** skill only. No need to load or loop over multiple Google sections.
|
|
27
|
-
|
|
28
|
-
Each skill doc has the full flow: list_integrations_connections with that provider β get token from gateway β curl the API.
|
package/context/hellohub.md
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: hellohub
|
|
3
|
-
description: hello
|
|
4
|
-
version: 1.0.0
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
name: hello-hub
|
|
9
|
-
description: Example skill for testing install from hub. Use when the user asks for a greeting or hello.
|
|
10
|
-
metadata:
|
|
11
|
-
{
|
|
12
|
-
"sulala": {
|
|
13
|
-
"requires": { "bins": [] }
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
# Hello Hub
|
|
19
|
-
|
|
20
|
-
Example skill to test installing from the hub.
|
|
21
|
-
|
|
22
|
-
Use when the user asks for:
|
|
23
|
-
- A greeting
|
|
24
|
-
- Hello from hub
|
|
25
|
-
- Testing hub install
|
|
26
|
-
|
|
27
|
-
## Usage
|
|
28
|
-
|
|
29
|
-
Respond with a friendly greeting. No external tools required.
|
package/context/jira.md
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: jira
|
|
3
|
-
description: Use Jira (sites, issues, transitions) via the Portal. When the user asks about Jira issues or projects, list connections with list_integrations_connections (provider jira) and use run_command with curl to the Jira API or gateway.
|
|
4
|
-
metadata:
|
|
5
|
-
{
|
|
6
|
-
"sulala": {
|
|
7
|
-
"emoji": "π§",
|
|
8
|
-
"requires": { "bins": ["curl"] }
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
# Jira
|
|
14
|
-
|
|
15
|
-
1. **list_integrations_connections** with `provider: "jira"` β get `connection_id`.
|
|
16
|
-
2. **get_connection_token** with that `connection_id` β returns `accessToken` (do not curl the portal).
|
|
17
|
-
3. **run_command (curl)** to the user's Jira site (`https://<site>.atlassian.net` or `https://api.atlassian.com/...`). All requests: `Authorization: Bearer <accessToken>`, `Content-Type: application/json`. Add `*.atlassian.net` (or the site host) to **ALLOWED_CURL_HOSTS**.
|
|
18
|
-
|
|
19
|
-
Official docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## Cloud ID / Access
|
|
24
|
-
|
|
25
|
-
- **Get accessible resources** (to find cloud ID): `GET https://api.atlassian.com/oauth/token/accessible-resources`. Returns `[].id` (cloudId), `[].url` (e.g. `https://site.atlassian.net`). Use cloud ID in REST v3 URLs: `https://api.atlassian.com/ex/jira/<cloudId>/rest/api/3/...`.
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## Search issues
|
|
30
|
-
|
|
31
|
-
- **Search**: `POST https://api.atlassian.com/ex/jira/<cloudId>/rest/api/3/search` with body `{"jql": "project = MYPROJECT ORDER BY created DESC", "maxResults": 20, "fields": ["summary", "status", "assignee"]}`. Returns `issues[].key`, `issues[].fields.summary`, `issues[].fields.status.name`. JQL examples: `assignee = currentUser()`, `status = "In Progress"`.
|
|
32
|
-
|
|
33
|
-
---
|
|
34
|
-
|
|
35
|
-
## Create issue
|
|
36
|
-
|
|
37
|
-
- **Create**: `POST https://api.atlassian.com/ex/jira/<cloudId>/rest/api/3/issue` with body `{"fields": {"project": {"key": "PROJ"}, "summary": "Title", "issuetype": {"name": "Task"}, "description": {"type": "doc", "version": 1, "content": [{"type": "paragraph", "content": [{"type": "text", "text": "Description"}]}]}}}`. Get project keys from `/rest/api/3/project`; issuetypes from `/rest/api/3/issue/createmeta`.
|
|
38
|
-
|
|
39
|
-
---
|
|
40
|
-
|
|
41
|
-
## Transitions
|
|
42
|
-
|
|
43
|
-
- **Get transitions** (for an issue): `GET https://api.atlassian.com/ex/jira/<cloudId>/rest/api/3/issue/<issueKey>/transitions`. Returns `transitions[].id`, `transitions[].name`.
|
|
44
|
-
- **Transition issue**: `POST https://api.atlassian.com/ex/jira/<cloudId>/rest/api/3/issue/<issueKey>/transitions` with body `{"transition": {"id": "<transitionId>"}}`.
|
|
45
|
-
|
|
46
|
-
Requirements: **PORTAL_GATEWAY_URL**, **PORTAL_API_KEY**; user must connect Jira in the Portal.
|
package/context/linear.md
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: linear
|
|
3
|
-
description: Use Linear (teams, issues) via the Portal. When the user asks about Linear teams or issues, list connections with list_integrations_connections (provider linear) and use run_command with curl to the Linear API or gateway.
|
|
4
|
-
metadata:
|
|
5
|
-
{
|
|
6
|
-
"sulala": {
|
|
7
|
-
"emoji": "π",
|
|
8
|
-
"requires": { "bins": ["curl"] }
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
# Linear
|
|
14
|
-
|
|
15
|
-
**Creating an issue:** First run the teams query to get a real team `id` (UUID). Then POST the issue with body `{"query": "mutation ... $teamId $title $description ...", "variables": {"teamId": "<from teams>", "title": "...", "description": "..."}}`. Never embed title or description in the query stringβuse variables only.
|
|
16
|
-
|
|
17
|
-
1. **list_integrations_connections** with `provider: "linear"` β get `connection_id`.
|
|
18
|
-
2. **get_connection_token** with that `connection_id` β returns `accessToken` (do not curl the portal).
|
|
19
|
-
3. **run_command (curl)** to `POST https://api.linear.app/graphql` with `Authorization: Bearer <accessToken>`, `Content-Type: application/json`, and body `{"query": "<GraphQL>", "variables": {...}}`. Use **variables** for any user-provided text (title, description) so quotes and newlines do not break the query.
|
|
20
|
-
|
|
21
|
-
Add `api.linear.app` to **ALLOWED_CURL_HOSTS**. Official docs: https://developers.linear.app/docs/graphql
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## Teams
|
|
26
|
-
|
|
27
|
-
- **List teams**: Query `query { teams { nodes { id name key } } }`. Returns `data.teams.nodes`.
|
|
28
|
-
|
|
29
|
-
---
|
|
30
|
-
|
|
31
|
-
## Issues
|
|
32
|
-
|
|
33
|
-
- **List issues** (filter by team or assignee): `query { issues(first: 20, filter: { team: { key: { eq: "ENG" } } }) { nodes { id identifier title state { name } assignee { name } } } }`. Or use `filter: { assignee: { id: { eq: "<userId>" } } }`.
|
|
34
|
-
|
|
35
|
-
- **Create issue** β you must do two steps. **Step 1:** Get a real team ID by sending a POST to `https://api.linear.app/graphql` with body `{"query":"query { teams { nodes { id name key } } }"}`. Use one of the returned `nodes[].id` values (UUID format like `23f232c2-7a1c-407e-8e6c-3c75bdfd0d41`). **Step 2:** Create the issue by POSTing to the same URL with a body that has **two keys only**: `query` (a mutation that uses variables, no literal title/description) and `variables` (JSON with the actual teamId, title, description). Do **not** put title or description inside the query stringβthat causes "Syntax Error: Unexpected }". Use this exact shape:
|
|
36
|
-
- Body: `{"query": "mutation IssueCreate($teamId: String!, $title: String!, $description: String) { issueCreate(input: { teamId: $teamId, title: $title, description: $description }) { success issue { id identifier url } } }", "variables": {"teamId": "<paste the team id from step 1>", "title": "Test Issue", "description": "Optional description"}}`
|
|
37
|
-
- The `query` string must contain only `$teamId`, `$title`, `$description`βno quoted literals for those. The `variables` object holds the real values.
|
|
38
|
-
- **Update issue** (e.g. state, assignee): `mutation { issueUpdate(id: "<issueId>", input: { stateId: "<stateId>" }) { success issue { id state { name } } } }`. List states with `query { workflowStates(filter: { team: { id: { eq: "<teamId>" } } }) { nodes { id name } } }`.
|
|
39
|
-
|
|
40
|
-
Requirements: **PORTAL_GATEWAY_URL**, **PORTAL_API_KEY**; user must connect Linear in the Portal.
|
package/context/news.md
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: news
|
|
3
|
-
description: Fetch news and articles via the Perigon API. Use when the user asks for news, headlines, or articles on a topic.
|
|
4
|
-
homepage: https://www.perigon.io
|
|
5
|
-
metadata:
|
|
6
|
-
{
|
|
7
|
-
"sulala": {
|
|
8
|
-
"emoji": "π°",
|
|
9
|
-
"requires": { "bins": ["curl"], "env": ["PERIGON_API_KEY"] }
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
# News (Perigon API)
|
|
15
|
-
|
|
16
|
-
Use **run_command** with `curl` to fetch articles from the Perigon API. Add `curl` to ALLOWED_BINARIES.
|
|
17
|
-
|
|
18
|
-
Requires `PERIGON_API_KEY`. Set it in `.env` or in the skill config (dashboard Skills page). Config key in `skills.entries.news` is `PERIGON_API_KEY`.
|
|
19
|
-
|
|
20
|
-
**IMPORTANT:** Use `binary: "sh"` and `args: ["-c", "curl ..."]` so `$PERIGON_API_KEY` expands, or pass the key in the URL when calling curl.
|
|
21
|
-
|
|
22
|
-
## When to Use
|
|
23
|
-
|
|
24
|
-
- "Get me the latest news"
|
|
25
|
-
- "Headlines about [topic]"
|
|
26
|
-
- "Find articles on [subject]"
|
|
27
|
-
|
|
28
|
-
## API
|
|
29
|
-
|
|
30
|
-
Base URL: `https://api.perigon.io/v1`
|
|
31
|
-
|
|
32
|
-
### All articles (recent)
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
curl -s -X GET "https://api.perigon.io/v1/articles/all?apiKey=$PERIGON_API_KEY" -H "Content-Type: application/json"
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
### With query (topic, keyword)
|
|
39
|
-
|
|
40
|
-
Append `&q=keyword` to the URL. Example:
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
curl -s -X GET "https://api.perigon.io/v1/articles/all?apiKey=$PERIGON_API_KEY&q=climate" -H "Content-Type: application/json"
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
### Reading key from config
|
|
47
|
-
|
|
48
|
-
If `PERIGON_API_KEY` is not set in the environment, read from Sulala config:
|
|
49
|
-
|
|
50
|
-
```
|
|
51
|
-
CONFIG_PATH="${SULALA_CONFIG_PATH:-$HOME/.sulala/config.json}"
|
|
52
|
-
PERIGON_API_KEY=$(cat "$CONFIG_PATH" 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); e=d.get('skills',{}).get('entries',{}).get('news',{}); print(e.get('PERIGON_API_KEY',''))" 2>/dev/null)
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
Then use `$PERIGON_API_KEY` in the curl URL.
|
|
56
|
-
|
|
57
|
-
## Response
|
|
58
|
-
|
|
59
|
-
Returns JSON with an array of articles (title, description, url, source, published date, etc.). Parse with `python3 -c "import sys,json; d=json.load(sys.stdin); ..."` to summarize or filter for the user.
|
|
60
|
-
|
|
61
|
-
## Notes
|
|
62
|
-
|
|
63
|
-
- Get an API key at https://www.perigon.io
|
|
64
|
-
- Add `api.perigon.io` to ALLOWED_CURL_HOSTS if you restrict curl by host.
|
package/context/notion.md
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: notion
|
|
3
|
-
description: Use Notion (search, pages, databases) via the Portal. When the user asks about Notion pages or databases, list connections with list_integrations_connections (provider notion) and use run_command with curl to the Notion API or gateway.
|
|
4
|
-
metadata:
|
|
5
|
-
{
|
|
6
|
-
"sulala": {
|
|
7
|
-
"emoji": "π",
|
|
8
|
-
"requires": { "bins": ["curl"] }
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
# Notion
|
|
14
|
-
|
|
15
|
-
1. **list_integrations_connections** with `provider: "notion"` β get `connection_id`.
|
|
16
|
-
2. **get_connection_token** with that `connection_id` β returns `accessToken` (do not curl the portal).
|
|
17
|
-
3. **run_command (curl)** with `Authorization: Bearer <accessToken>`, `Notion-Version: 2022-06-28`, and `Content-Type: application/json` where applicable.
|
|
18
|
-
|
|
19
|
-
Base URL: `https://api.notion.com/v1`. Add `api.notion.com` to **ALLOWED_CURL_HOSTS**.
|
|
20
|
-
|
|
21
|
-
Official docs: https://developers.notion.com/reference
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## Search
|
|
26
|
-
|
|
27
|
-
- **Search** (pages and databases): `POST https://api.notion.com/v1/search` with body `{"query": "optional search text", "filter": {"property": "object", "value": "page"}}` or `"value": "database"`. Returns `results[].id`, `url`, `object` (page/database). Omit filter to get both.
|
|
28
|
-
|
|
29
|
-
---
|
|
30
|
-
|
|
31
|
-
## Pages
|
|
32
|
-
|
|
33
|
-
- **Get page** (content and properties): `GET https://api.notion.com/v1/pages/<page_id>`. Page ID is the UUID from a Notion URL (with hyphens).
|
|
34
|
-
- **Get page content** (blocks): `GET https://api.notion.com/v1/blocks/<page_id>/children?page_size=100`. Returns block objects (paragraph, heading, etc.); `type` and `paragraph.rich_text` or similar.
|
|
35
|
-
- **Create page** (under parent): `POST https://api.notion.com/v1/pages` with body `{"parent": {"page_id": "<parent_page_id>"}, "properties": {"title": {"title": [{"text": {"content": "Page title"}}]}}}`. Parent page must be shared with the integration.
|
|
36
|
-
- **Update page** (e.g. title): `PATCH https://api.notion.com/v1/pages/<page_id>` with body `{"properties": {"title": {"title": [{"text": {"content": "New title"}}]}}}`.
|
|
37
|
-
|
|
38
|
-
---
|
|
39
|
-
|
|
40
|
-
## Databases
|
|
41
|
-
|
|
42
|
-
- **Create database** (under parent page): `POST https://api.notion.com/v1/databases` with body `{"parent": {"page_id": "<parent_page_id>"}, "title": [{"text": {"content": "DB name"}}], "properties": {"Name": {"title": {}}}}`. Default "Name" property is created.
|
|
43
|
-
- **Query database** (list rows): `POST https://api.notion.com/v1/databases/<database_id>/query` with body `{}` or `{"filter": {...}, "sorts": [{"property": "Name", "direction": "ascending"}]}`. Returns `results[]` (page objects with properties).
|
|
44
|
-
|
|
45
|
-
Requirements: **PORTAL_GATEWAY_URL**, **PORTAL_API_KEY**; user must connect Notion in the Portal. Pages/databases must be shared with the connected Notion integration.
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: portal-integrations
|
|
3
|
-
description: Use connected apps (Gmail, Calendar, Zoom, Slack, GitHub, etc.) via the Portal. When the user asks about email, calendar, meetings, or other integrated apps, list connections with list_integrations_connections and use run_command with curl to the gateway or provider APIs (see per-integration skills: calendar, gmail, drive, docs, sheets, slides, github, notion, slack, linear, zoom, airtable, etc.).
|
|
4
|
-
metadata:
|
|
5
|
-
{
|
|
6
|
-
"sulala": {
|
|
7
|
-
"emoji": "π"
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
# Portal integrations
|
|
13
|
-
|
|
14
|
-
The agent uses **connected apps** (Gmail, Google Calendar, Zoom, Slack, GitHub, Notion, Linear, Airtable, etc.) via **skills** and **run_command** with curl. The user connects apps in the Portal (or dashboard β Integrations); the agent gets `connection_id` from **list_integrations_connections** and performs actions by following the relevant integration skill (e.g. calendar.md, gmail.md, drive.md, github.md, slack.md) and calling **run_command** with `binary: "curl"` to the gateway or provider API.
|
|
15
|
-
|
|
16
|
-
## How it works
|
|
17
|
-
|
|
18
|
-
1. **User** creates an API key at the Portal and adds it in the agent (Settings β Portal API key or `PORTAL_API_KEY`). Optionally set `PORTAL_GATEWAY_URL`.
|
|
19
|
-
2. **User** connects apps in the Portal (or dashboard Integrations): Gmail, Calendar, Zoom, Slack, etc.
|
|
20
|
-
3. **Agent** uses:
|
|
21
|
-
- **list_integrations_connections** β returns `connection_id` and `provider` for each connected app. Call with optional `provider` to filter (e.g. `"calendar"`, `"gmail"`, `"slack"`, `"github"`).
|
|
22
|
-
- **get_connection_token** β call with `connection_id` to get an OAuth `accessToken`. Use this before each integration API call; the agent does not curl the portal from run_command.
|
|
23
|
-
- **run_command** with **curl** β use the `accessToken` from get_connection_token in the `Authorization: Bearer <accessToken>` header when curling the provider API (Gmail, Calendar, GitHub, Slack, etc.), as documented in each integration skill.
|
|
24
|
-
|
|
25
|
-
Integration behavior is **skill-driven**: use **list_integrations_connections** for OAuth apps (calendar, gmail, drive, github, slack, linear, etc.) and the instructions in the per-integration skills. **Stripe and Discord are not OAuth**βthey are "channels" configured in **Settings β Channels** (API key / bot token). Do not use list_integrations_connections for Stripe or Discord; use **stripe_list_customers** and **discord_list_guilds** / **discord_send_message** instead. See stripe.md and discord.md.
|
|
26
|
-
|
|
27
|
-
## When to use
|
|
28
|
-
|
|
29
|
-
- User asks to **create a calendar event**, read/send email, check calendar, list or create Zoom meetings, post in Slack, list GitHub repos/issues, query Notion, etc. For calendar events, always use the **calendar** skill (provider `calendar`) and the Calendar API via run_command + curl; do not use Apple Calendar, osascript, or local calendar apps.
|
|
30
|
-
- First call **list_integrations_connections** (optionally with `provider`, e.g. `calendar` for events), then follow the relevant integration skill and use **run_command** with curl as documented there.
|
|
31
|
-
|
|
32
|
-
## Requirements
|
|
33
|
-
|
|
34
|
-
- **PORTAL_GATEWAY_URL** and **PORTAL_API_KEY** must be set so the agent can list connections and get OAuth tokens. The agent loads them from **`~/.sulala/.env`** (e.g. when saved via dashboard Settings); you do not need them in the agent project `.env`. If both exist, the agent project `.env` overrides. Create the API key in the Portal β API Keys. Example in `~/.sulala/.env`: `PORTAL_GATEWAY_URL=https://portal.sulala.ai/api/gateway`, `PORTAL_API_KEY=<your key>`.
|
|
35
|
-
- The user must have connected the relevant app in the Portal before the agent can use it.
|
|
36
|
-
- Add required hosts to **ALLOWED_CURL_HOSTS** (e.g. api.github.com, slack.com, www.googleapis.com) as documented in each integration skill.
|
|
37
|
-
|
|
38
|
-
## Notes
|
|
39
|
-
|
|
40
|
-
- Connection count and subscription limits are enforced by the Portal; the agent only sees connections the user has already connected.
|
|
41
|
-
- If **list_integrations_connections** returns an error like "Portal not configured", prompt the user to add a Portal API key in Settings or at the Portal.
|
|
42
|
-
- For OAuth-backed actions, the Portal (or gateway) may expose action endpoints (e.g. `POST /api/gateway/actions/gmail/send`) that skills document for curl; until then, skills document calling the gateway for a token and then the provider API directly.
|