@sulala/agent 0.1.6 β†’ 0.1.8

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 (173) hide show
  1. package/README.md +42 -27
  2. package/context/airtable.md +35 -0
  3. package/context/asana.md +37 -0
  4. package/context/bluesky.md +26 -91
  5. package/context/calendar.md +63 -0
  6. package/context/country-info.md +13 -0
  7. package/context/create-skill.md +128 -0
  8. package/context/discord.md +30 -0
  9. package/context/docs.md +29 -0
  10. package/context/drive.md +49 -0
  11. package/context/dropbox.md +39 -0
  12. package/context/facebook.md +47 -0
  13. package/context/fetch-form-api.md +16 -0
  14. package/context/figma.md +30 -0
  15. package/context/github.md +58 -0
  16. package/context/gmail.md +52 -0
  17. package/context/google.md +28 -0
  18. package/context/hellohub.md +29 -0
  19. package/context/jira.md +46 -0
  20. package/context/linear.md +40 -0
  21. package/context/notion.md +45 -0
  22. package/context/portal-integrations.md +42 -0
  23. package/context/post-to-x.md +50 -0
  24. package/context/sheets.md +47 -0
  25. package/context/slack.md +48 -0
  26. package/context/slides.md +35 -0
  27. package/context/stripe.md +38 -0
  28. package/context/tes.md +7 -0
  29. package/context/test.md +7 -0
  30. package/context/zoom.md +28 -0
  31. package/dist/agent/google/calendar.d.ts +2 -0
  32. package/dist/agent/google/calendar.d.ts.map +1 -0
  33. package/dist/agent/google/calendar.js +119 -0
  34. package/dist/agent/google/calendar.js.map +1 -0
  35. package/dist/agent/google/drive.d.ts +2 -0
  36. package/dist/agent/google/drive.d.ts.map +1 -0
  37. package/dist/agent/google/drive.js +51 -0
  38. package/dist/agent/google/drive.js.map +1 -0
  39. package/dist/agent/google/get-token.d.ts +7 -0
  40. package/dist/agent/google/get-token.d.ts.map +1 -0
  41. package/dist/agent/google/get-token.js +37 -0
  42. package/dist/agent/google/get-token.js.map +1 -0
  43. package/dist/agent/google/gmail.d.ts +2 -0
  44. package/dist/agent/google/gmail.d.ts.map +1 -0
  45. package/dist/agent/google/gmail.js +138 -0
  46. package/dist/agent/google/gmail.js.map +1 -0
  47. package/dist/agent/google/index.d.ts +2 -0
  48. package/dist/agent/google/index.d.ts.map +1 -0
  49. package/dist/agent/google/index.js +13 -0
  50. package/dist/agent/google/index.js.map +1 -0
  51. package/dist/agent/loop.d.ts +8 -0
  52. package/dist/agent/loop.d.ts.map +1 -1
  53. package/dist/agent/loop.js +226 -40
  54. package/dist/agent/loop.js.map +1 -1
  55. package/dist/agent/memory.d.ts +21 -0
  56. package/dist/agent/memory.d.ts.map +1 -0
  57. package/dist/agent/memory.js +33 -0
  58. package/dist/agent/memory.js.map +1 -0
  59. package/dist/agent/pending-actions.d.ts +21 -0
  60. package/dist/agent/pending-actions.d.ts.map +1 -0
  61. package/dist/agent/pending-actions.js +65 -0
  62. package/dist/agent/pending-actions.js.map +1 -0
  63. package/dist/agent/pi-runner.d.ts +27 -0
  64. package/dist/agent/pi-runner.d.ts.map +1 -0
  65. package/dist/agent/pi-runner.js +300 -0
  66. package/dist/agent/pi-runner.js.map +1 -0
  67. package/dist/agent/skill-generate.d.ts +63 -0
  68. package/dist/agent/skill-generate.d.ts.map +1 -0
  69. package/dist/agent/skill-generate.js +128 -0
  70. package/dist/agent/skill-generate.js.map +1 -0
  71. package/dist/agent/skill-install.d.ts.map +1 -1
  72. package/dist/agent/skill-install.js +80 -31
  73. package/dist/agent/skill-install.js.map +1 -1
  74. package/dist/agent/skill-templates.d.ts +17 -0
  75. package/dist/agent/skill-templates.d.ts.map +1 -0
  76. package/dist/agent/skill-templates.js +26 -0
  77. package/dist/agent/skill-templates.js.map +1 -0
  78. package/dist/agent/skills-config.d.ts +24 -2
  79. package/dist/agent/skills-config.d.ts.map +1 -1
  80. package/dist/agent/skills-config.js +108 -9
  81. package/dist/agent/skills-config.js.map +1 -1
  82. package/dist/agent/skills-watcher.js +1 -1
  83. package/dist/agent/skills.d.ts +9 -3
  84. package/dist/agent/skills.d.ts.map +1 -1
  85. package/dist/agent/skills.js +104 -9
  86. package/dist/agent/skills.js.map +1 -1
  87. package/dist/agent/tools.d.ts +25 -3
  88. package/dist/agent/tools.d.ts.map +1 -1
  89. package/dist/agent/tools.integrations.test.d.ts +2 -0
  90. package/dist/agent/tools.integrations.test.d.ts.map +1 -0
  91. package/dist/agent/tools.integrations.test.js +269 -0
  92. package/dist/agent/tools.integrations.test.js.map +1 -0
  93. package/dist/agent/tools.js +692 -39
  94. package/dist/agent/tools.js.map +1 -1
  95. package/dist/ai/orchestrator.d.ts +6 -1
  96. package/dist/ai/orchestrator.d.ts.map +1 -1
  97. package/dist/ai/orchestrator.js +499 -212
  98. package/dist/ai/orchestrator.js.map +1 -1
  99. package/dist/ai/pricing.d.ts +6 -0
  100. package/dist/ai/pricing.d.ts.map +1 -0
  101. package/dist/ai/pricing.js +39 -0
  102. package/dist/ai/pricing.js.map +1 -0
  103. package/dist/channels/discord.d.ts +15 -0
  104. package/dist/channels/discord.d.ts.map +1 -0
  105. package/dist/channels/discord.js +55 -0
  106. package/dist/channels/discord.js.map +1 -0
  107. package/dist/channels/stripe.d.ts +15 -0
  108. package/dist/channels/stripe.d.ts.map +1 -0
  109. package/dist/channels/stripe.js +58 -0
  110. package/dist/channels/stripe.js.map +1 -0
  111. package/dist/channels/telegram.d.ts +60 -0
  112. package/dist/channels/telegram.d.ts.map +1 -0
  113. package/dist/channels/telegram.js +562 -0
  114. package/dist/channels/telegram.js.map +1 -0
  115. package/dist/cli.js +69 -11
  116. package/dist/cli.js.map +1 -1
  117. package/dist/config.d.ts +14 -0
  118. package/dist/config.d.ts.map +1 -1
  119. package/dist/config.js +91 -2
  120. package/dist/config.js.map +1 -1
  121. package/dist/db/index.d.ts +83 -0
  122. package/dist/db/index.d.ts.map +1 -1
  123. package/dist/db/index.js +174 -2
  124. package/dist/db/index.js.map +1 -1
  125. package/dist/db/schema.sql +35 -0
  126. package/dist/gateway/server.d.ts.map +1 -1
  127. package/dist/gateway/server.js +1224 -29
  128. package/dist/gateway/server.js.map +1 -1
  129. package/dist/index.js +149 -6
  130. package/dist/index.js.map +1 -1
  131. package/dist/ollama-setup.d.ts +27 -0
  132. package/dist/ollama-setup.d.ts.map +1 -0
  133. package/dist/ollama-setup.js +191 -0
  134. package/dist/ollama-setup.js.map +1 -0
  135. package/dist/onboard-env.d.ts +1 -1
  136. package/dist/onboard-env.d.ts.map +1 -1
  137. package/dist/onboard-env.js +3 -0
  138. package/dist/onboard-env.js.map +1 -1
  139. package/dist/onboard.d.ts +3 -1
  140. package/dist/onboard.d.ts.map +1 -1
  141. package/dist/onboard.js +9 -4
  142. package/dist/onboard.js.map +1 -1
  143. package/dist/plugins/index.d.ts +10 -0
  144. package/dist/plugins/index.d.ts.map +1 -1
  145. package/dist/plugins/index.js +32 -0
  146. package/dist/plugins/index.js.map +1 -1
  147. package/dist/redact.d.ts +15 -0
  148. package/dist/redact.d.ts.map +1 -0
  149. package/dist/redact.js +56 -0
  150. package/dist/redact.js.map +1 -0
  151. package/dist/scheduler/cron.d.ts +21 -0
  152. package/dist/scheduler/cron.d.ts.map +1 -1
  153. package/dist/scheduler/cron.js +60 -0
  154. package/dist/scheduler/cron.js.map +1 -1
  155. package/dist/system-capabilities.d.ts +11 -0
  156. package/dist/system-capabilities.d.ts.map +1 -0
  157. package/dist/system-capabilities.js +109 -0
  158. package/dist/system-capabilities.js.map +1 -0
  159. package/dist/types.d.ts +62 -3
  160. package/dist/types.d.ts.map +1 -1
  161. package/dist/watcher/index.d.ts +2 -0
  162. package/dist/watcher/index.d.ts.map +1 -1
  163. package/dist/watcher/index.js +31 -1
  164. package/dist/watcher/index.js.map +1 -1
  165. package/dist/workspace-automations.d.ts +16 -0
  166. package/dist/workspace-automations.d.ts.map +1 -0
  167. package/dist/workspace-automations.js +133 -0
  168. package/dist/workspace-automations.js.map +1 -0
  169. package/package.json +19 -3
  170. package/registry/bluesky.md +12 -89
  171. package/registry/skills-registry.json +6 -0
  172. package/src/db/schema.sql +35 -0
  173. package/src/index.ts +159 -6
@@ -0,0 +1,49 @@
1
+ ---
2
+ name: drive
3
+ description: Use Google Drive via the Portal. When the user asks to list files, create a folder, upload/download files, or manage Drive content, use this skill with list_integrations_connections (provider drive) and run_command + curl.
4
+ metadata:
5
+ {
6
+ "sulala": {
7
+ "emoji": "πŸ“",
8
+ "requires": { "bins": ["curl"] }
9
+ }
10
+ }
11
+ ---
12
+
13
+ # Google Drive
14
+
15
+ Use **list_integrations_connections** with `provider: "drive"`, then **get_connection_token** (do not curl the portal). Then call Drive with that token.
16
+
17
+ 1. **list_integrations_connections** with `provider: "drive"` β†’ get `connection_id`.
18
+ 2. **get_connection_token** with that `connection_id` β†’ returns `accessToken`.
19
+ 3. **run_command (curl)** with `Authorization: Bearer <accessToken>` for all requests below.
20
+
21
+ Add `www.googleapis.com` to **ALLOWED_CURL_HOSTS**.
22
+
23
+ Base URL: `https://www.googleapis.com/drive/v3`
24
+
25
+ ---
26
+
27
+ ## List files
28
+
29
+ `GET https://www.googleapis.com/drive/v3/files?pageSize=20&q=<query>` (e.g. `q='root' in parents` for root; `q=trashed=false`). Returns `files[].id`, `name`, `mimeType`.
30
+
31
+ ---
32
+
33
+ ## Create folder
34
+
35
+ `POST https://www.googleapis.com/drive/v3/files` with body `{"name": "FolderName", "mimeType": "application/vnd.google-apps.folder"}`. Optional: `"parents": ["<folderId>"]`.
36
+
37
+ ---
38
+
39
+ ## Upload file (small)
40
+
41
+ Use multipart: one part JSON `{"name": "filename.txt", "parents": ["<folderId>"]}`, second part file content. Or use resumable upload (see Drive API docs).
42
+
43
+ ---
44
+
45
+ ## Download file
46
+
47
+ `GET https://www.googleapis.com/drive/v3/files/<fileId>?alt=media` with `Authorization: Bearer <token>` (binary response). For export of Google Docs/Sheets use `GET https://www.googleapis.com/drive/v3/files/<fileId>/export?mimeType=text/plain` (or other export type).
48
+
49
+ Requirements: **PORTAL_GATEWAY_URL**, **PORTAL_API_KEY**; user must connect Google Drive in the Portal or dashboard Integrations.
@@ -0,0 +1,39 @@
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.
@@ -0,0 +1,47 @@
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`.
@@ -0,0 +1,16 @@
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.
@@ -0,0 +1,30 @@
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.
@@ -0,0 +1,58 @@
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.
@@ -0,0 +1,52 @@
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.
@@ -0,0 +1,28 @@
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.
@@ -0,0 +1,29 @@
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.
@@ -0,0 +1,46 @@
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.
@@ -0,0 +1,40 @@
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.
@@ -0,0 +1,45 @@
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.
@@ -0,0 +1,42 @@
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.
@@ -0,0 +1,50 @@
1
+ ---
2
+ name: post-to-x
3
+ description: >
4
+ Posts a tweet to X (formerly Twitter) using OAuth 1.0a user authentication.
5
+ Use this skill when the user asks to tweet, post to X/Twitter, or share an update on X.
6
+ metadata:
7
+ {
8
+ "sulala": {
9
+ "requires": {
10
+ "bins": ["python3"],
11
+ "env": [
12
+ "X_API_KEY",
13
+ "X_API_SECRET",
14
+ "X_ACCESS_TOKEN",
15
+ "X_ACCESS_TOKEN_SECRET"
16
+ ]
17
+ },
18
+ "primaryEnv": "X_ACCESS_TOKEN",
19
+ "capabilities": ["write", "social", "automation"]
20
+ }
21
+ }
22
+ ---
23
+
24
+ # Post to X (Twitter)
25
+
26
+ This skill posts a tweet to **:contentReference[oaicite:0]{index=0}** using **OAuth 1.0a (User Context)** authentication.
27
+
28
+ ⚠️ **Important**
29
+ - App-only Bearer Tokens **cannot** post tweets.
30
+ - This skill requires **user-level OAuth 1.0a credentials** with **Read & Write** permissions.
31
+
32
+ ---
33
+
34
+ ## When to Use
35
+ Use this skill when the user asks to:
36
+ - Post a tweet
37
+ - Share content on X
38
+ - Announce something on Twitter/X
39
+ - Publish automated updates
40
+
41
+ ---
42
+
43
+ ## Requirements
44
+
45
+ ### Environment Variables
46
+ ```env
47
+ X_API_KEY=your_consumer_key
48
+ X_API_SECRET=your_consumer_secret
49
+ X_ACCESS_TOKEN=your_access_token
50
+ X_ACCESS_TOKEN_SECRET=your_access_token_secret
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: sheets
3
+ description: Use Google Sheets via the Portal. When the user asks to list spreadsheets, read or append rows, or edit Sheets, use this skill with list_integrations_connections (provider sheets) and run_command + curl.
4
+ metadata:
5
+ {
6
+ "sulala": {
7
+ "emoji": "πŸ“Š",
8
+ "requires": { "bins": ["curl"] }
9
+ }
10
+ }
11
+ ---
12
+
13
+ # Google Sheets
14
+
15
+ Use **list_integrations_connections** with `provider: "sheets"`, then **get_connection_token** (do not curl the portal). Then call the API with that token.
16
+
17
+ 1. **list_integrations_connections** with `provider: "sheets"` β†’ get `connection_id`.
18
+ 2. **get_connection_token** with that `connection_id` β†’ returns `accessToken`.
19
+ 3. **run_command (curl)** with `Authorization: Bearer <accessToken>` for all requests.
20
+
21
+ Add `www.googleapis.com` and `sheets.googleapis.com` to **ALLOWED_CURL_HOSTS**.
22
+
23
+ ---
24
+
25
+ ## List spreadsheets
26
+
27
+ List via Drive API (mimeType `application/vnd.google-apps.spreadsheet`).
28
+
29
+ ---
30
+
31
+ ## Read range
32
+
33
+ `GET https://sheets.googleapis.com/v4/spreadsheets/<spreadsheetId>/values/<range>`. Example range: `Sheet1!A1:D10`.
34
+
35
+ ---
36
+
37
+ ## Append rows
38
+
39
+ `POST https://sheets.googleapis.com/v4/spreadsheets/<spreadsheetId>/values/<range>:append?valueInputOption=USER_ENTERED` with body `{"values": [[ "cell1", "cell2" ], [ "row2col1", "row2col2" ]]}`.
40
+
41
+ ---
42
+
43
+ ## Update range
44
+
45
+ `PUT https://sheets.googleapis.com/v4/spreadsheets/<spreadsheetId>/values/<range>?valueInputOption=USER_ENTERED` with body `{"values": [[...]]}`.
46
+
47
+ Requirements: **PORTAL_GATEWAY_URL**, **PORTAL_API_KEY**; user must connect Google Sheets in the Portal or dashboard Integrations.