@sulala/agent 0.1.13 β†’ 0.1.15

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 (102) hide show
  1. package/README.md +3 -3
  2. package/dashboard/dist/assets/index-DegBJNv6.css +1 -0
  3. package/dashboard/dist/assets/index-pVHpAj3h.js +83 -0
  4. package/dashboard/dist/index.html +2 -2
  5. package/dist/agent/loop.d.ts.map +1 -1
  6. package/dist/agent/loop.js +21 -6
  7. package/dist/agent/loop.js.map +1 -1
  8. package/dist/agent/skill-generate.d.ts +1 -1
  9. package/dist/agent/skill-generate.d.ts.map +1 -1
  10. package/dist/agent/skill-generate.js +10 -3
  11. package/dist/agent/skill-generate.js.map +1 -1
  12. package/dist/agent/skill-install.d.ts +12 -1
  13. package/dist/agent/skill-install.d.ts.map +1 -1
  14. package/dist/agent/skill-install.js +130 -15
  15. package/dist/agent/skill-install.js.map +1 -1
  16. package/dist/agent/skills.d.ts +4 -3
  17. package/dist/agent/skills.d.ts.map +1 -1
  18. package/dist/agent/skills.js +53 -25
  19. package/dist/agent/skills.js.map +1 -1
  20. package/dist/agent/tool/spec-loader.d.ts +7 -0
  21. package/dist/agent/tool/spec-loader.d.ts.map +1 -0
  22. package/dist/agent/tool/spec-loader.js +540 -0
  23. package/dist/agent/tool/spec-loader.js.map +1 -0
  24. package/dist/agent/tools.d.ts.map +1 -1
  25. package/dist/agent/tools.integrations.test.js +4 -5
  26. package/dist/agent/tools.integrations.test.js.map +1 -1
  27. package/dist/agent/tools.js +144 -367
  28. package/dist/agent/tools.js.map +1 -1
  29. package/dist/ai/orchestrator.d.ts.map +1 -1
  30. package/dist/ai/orchestrator.js +82 -17
  31. package/dist/ai/orchestrator.js.map +1 -1
  32. package/dist/cli.d.ts +4 -1
  33. package/dist/cli.d.ts.map +1 -1
  34. package/dist/cli.js +25 -9
  35. package/dist/cli.js.map +1 -1
  36. package/dist/config.d.ts.map +1 -1
  37. package/dist/config.js +20 -5
  38. package/dist/config.js.map +1 -1
  39. package/dist/db/index.d.ts +14 -7
  40. package/dist/db/index.d.ts.map +1 -1
  41. package/dist/db/index.js +108 -30
  42. package/dist/db/index.js.map +1 -1
  43. package/dist/gateway/server.d.ts.map +1 -1
  44. package/dist/gateway/server.js +141 -15
  45. package/dist/gateway/server.js.map +1 -1
  46. package/dist/index.js +1 -1
  47. package/dist/index.js.map +1 -1
  48. package/dist/onboard-env.d.ts +1 -1
  49. package/dist/onboard-env.d.ts.map +1 -1
  50. package/dist/onboard-env.js +2 -0
  51. package/dist/onboard-env.js.map +1 -1
  52. package/dist/types.d.ts +5 -3
  53. package/dist/types.d.ts.map +1 -1
  54. package/dist/watcher/index.d.ts.map +1 -1
  55. package/dist/watcher/index.js +1 -2
  56. package/dist/watcher/index.js.map +1 -1
  57. package/package.json +4 -5
  58. package/src/index.ts +1 -1
  59. package/context/00-rules.md +0 -1
  60. package/context/airtable.md +0 -35
  61. package/context/apple-notes.md +0 -99
  62. package/context/asana.md +0 -37
  63. package/context/bluesky.md +0 -46
  64. package/context/calendar.md +0 -63
  65. package/context/country-info.md +0 -13
  66. package/context/create-skill.md +0 -128
  67. package/context/discord.md +0 -30
  68. package/context/docs.md +0 -29
  69. package/context/drive.md +0 -49
  70. package/context/dropbox.md +0 -39
  71. package/context/facebook.md +0 -47
  72. package/context/fetch-form-api.md +0 -16
  73. package/context/figma.md +0 -30
  74. package/context/files.md +0 -30
  75. package/context/git.md +0 -37
  76. package/context/github.md +0 -58
  77. package/context/gmail.md +0 -52
  78. package/context/google.md +0 -28
  79. package/context/hellohub.md +0 -29
  80. package/context/jira.md +0 -46
  81. package/context/linear.md +0 -40
  82. package/context/news.md +0 -64
  83. package/context/notion.md +0 -45
  84. package/context/portal-integrations.md +0 -42
  85. package/context/post-to-x.md +0 -50
  86. package/context/sheets.md +0 -47
  87. package/context/slack.md +0 -48
  88. package/context/slides.md +0 -35
  89. package/context/stripe.md +0 -38
  90. package/context/tes.md +0 -7
  91. package/context/test.md +0 -7
  92. package/context/weather.md +0 -32
  93. package/context/zoom.md +0 -28
  94. package/dashboard/dist/assets/index-BTx-9jCj.css +0 -1
  95. package/dashboard/dist/assets/index-B_QGQ8c_.js +0 -83
  96. package/registry/apple-notes.md +0 -99
  97. package/registry/bluesky.md +0 -34
  98. package/registry/files.md +0 -30
  99. package/registry/git.md +0 -37
  100. package/registry/news.md +0 -64
  101. package/registry/skills-registry.json +0 -46
  102. package/registry/weather.md +0 -32
@@ -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.
@@ -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.
@@ -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.