@sulala/agent 0.1.5 → 0.1.7
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 -2
- package/context/airtable.md +35 -0
- package/context/asana.md +37 -0
- package/context/bluesky.md +26 -91
- package/context/calendar.md +63 -0
- package/context/country-info.md +13 -0
- package/context/create-skill.md +128 -0
- package/context/discord.md +30 -0
- package/context/docs.md +29 -0
- package/context/drive.md +49 -0
- package/context/dropbox.md +39 -0
- package/context/facebook.md +47 -0
- package/context/fetch-form-api.md +16 -0
- package/context/figma.md +30 -0
- package/context/github.md +58 -0
- package/context/gmail.md +52 -0
- package/context/google.md +28 -0
- package/context/hellohub.md +29 -0
- package/context/jira.md +46 -0
- package/context/linear.md +40 -0
- package/context/notion.md +45 -0
- package/context/portal-integrations.md +42 -0
- package/context/post-to-x.md +50 -0
- package/context/sheets.md +47 -0
- package/context/slack.md +48 -0
- package/context/slides.md +35 -0
- package/context/stripe.md +38 -0
- package/context/tes.md +7 -0
- package/context/test.md +7 -0
- package/context/zoom.md +28 -0
- package/dist/agent/google/calendar.d.ts +2 -0
- package/dist/agent/google/calendar.d.ts.map +1 -0
- package/dist/agent/google/calendar.js +119 -0
- package/dist/agent/google/calendar.js.map +1 -0
- package/dist/agent/google/drive.d.ts +2 -0
- package/dist/agent/google/drive.d.ts.map +1 -0
- package/dist/agent/google/drive.js +51 -0
- package/dist/agent/google/drive.js.map +1 -0
- package/dist/agent/google/get-token.d.ts +7 -0
- package/dist/agent/google/get-token.d.ts.map +1 -0
- package/dist/agent/google/get-token.js +37 -0
- package/dist/agent/google/get-token.js.map +1 -0
- package/dist/agent/google/gmail.d.ts +2 -0
- package/dist/agent/google/gmail.d.ts.map +1 -0
- package/dist/agent/google/gmail.js +138 -0
- package/dist/agent/google/gmail.js.map +1 -0
- package/dist/agent/google/index.d.ts +2 -0
- package/dist/agent/google/index.d.ts.map +1 -0
- package/dist/agent/google/index.js +13 -0
- package/dist/agent/google/index.js.map +1 -0
- package/dist/agent/loop.d.ts +8 -0
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +226 -40
- package/dist/agent/loop.js.map +1 -1
- package/dist/agent/memory.d.ts +21 -0
- package/dist/agent/memory.d.ts.map +1 -0
- package/dist/agent/memory.js +33 -0
- package/dist/agent/memory.js.map +1 -0
- package/dist/agent/pending-actions.d.ts +21 -0
- package/dist/agent/pending-actions.d.ts.map +1 -0
- package/dist/agent/pending-actions.js +65 -0
- package/dist/agent/pending-actions.js.map +1 -0
- package/dist/agent/pi-runner.d.ts +27 -0
- package/dist/agent/pi-runner.d.ts.map +1 -0
- package/dist/agent/pi-runner.js +300 -0
- package/dist/agent/pi-runner.js.map +1 -0
- package/dist/agent/skill-generate.d.ts +63 -0
- package/dist/agent/skill-generate.d.ts.map +1 -0
- package/dist/agent/skill-generate.js +128 -0
- package/dist/agent/skill-generate.js.map +1 -0
- package/dist/agent/skill-install.d.ts.map +1 -1
- package/dist/agent/skill-install.js +80 -31
- package/dist/agent/skill-install.js.map +1 -1
- package/dist/agent/skill-templates.d.ts +17 -0
- package/dist/agent/skill-templates.d.ts.map +1 -0
- package/dist/agent/skill-templates.js +26 -0
- package/dist/agent/skill-templates.js.map +1 -0
- package/dist/agent/skills-config.d.ts +24 -2
- package/dist/agent/skills-config.d.ts.map +1 -1
- package/dist/agent/skills-config.js +107 -8
- package/dist/agent/skills-config.js.map +1 -1
- package/dist/agent/skills-watcher.js +1 -1
- package/dist/agent/skills.d.ts +9 -3
- package/dist/agent/skills.d.ts.map +1 -1
- package/dist/agent/skills.js +104 -9
- package/dist/agent/skills.js.map +1 -1
- package/dist/agent/tools.d.ts +25 -3
- package/dist/agent/tools.d.ts.map +1 -1
- package/dist/agent/tools.integrations.test.d.ts +2 -0
- package/dist/agent/tools.integrations.test.d.ts.map +1 -0
- package/dist/agent/tools.integrations.test.js +269 -0
- package/dist/agent/tools.integrations.test.js.map +1 -0
- package/dist/agent/tools.js +692 -39
- package/dist/agent/tools.js.map +1 -1
- package/dist/ai/orchestrator.d.ts +4 -1
- package/dist/ai/orchestrator.d.ts.map +1 -1
- package/dist/ai/orchestrator.js +246 -14
- package/dist/ai/orchestrator.js.map +1 -1
- package/dist/ai/pricing.d.ts +6 -0
- package/dist/ai/pricing.d.ts.map +1 -0
- package/dist/ai/pricing.js +39 -0
- package/dist/ai/pricing.js.map +1 -0
- package/dist/channels/discord.d.ts +15 -0
- package/dist/channels/discord.d.ts.map +1 -0
- package/dist/channels/discord.js +55 -0
- package/dist/channels/discord.js.map +1 -0
- package/dist/channels/stripe.d.ts +15 -0
- package/dist/channels/stripe.d.ts.map +1 -0
- package/dist/channels/stripe.js +58 -0
- package/dist/channels/stripe.js.map +1 -0
- package/dist/channels/telegram.d.ts +60 -0
- package/dist/channels/telegram.d.ts.map +1 -0
- package/dist/channels/telegram.js +562 -0
- package/dist/channels/telegram.js.map +1 -0
- package/dist/cli.js +74 -10
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +14 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +85 -1
- package/dist/config.js.map +1 -1
- package/dist/db/index.d.ts +83 -0
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +174 -2
- package/dist/db/index.js.map +1 -1
- package/dist/db/schema.sql +35 -0
- package/dist/gateway/server.d.ts.map +1 -1
- package/dist/gateway/server.js +1219 -27
- package/dist/gateway/server.js.map +1 -1
- package/dist/index.js +149 -6
- package/dist/index.js.map +1 -1
- package/dist/ollama-setup.d.ts +27 -0
- package/dist/ollama-setup.d.ts.map +1 -0
- package/dist/ollama-setup.js +191 -0
- package/dist/ollama-setup.js.map +1 -0
- 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/onboard.d.ts +3 -1
- package/dist/onboard.d.ts.map +1 -1
- package/dist/onboard.js +7 -2
- package/dist/onboard.js.map +1 -1
- package/dist/plugins/index.d.ts +10 -0
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +32 -0
- package/dist/plugins/index.js.map +1 -1
- package/dist/redact.d.ts +15 -0
- package/dist/redact.d.ts.map +1 -0
- package/dist/redact.js +56 -0
- package/dist/redact.js.map +1 -0
- package/dist/scheduler/cron.d.ts +21 -0
- package/dist/scheduler/cron.d.ts.map +1 -1
- package/dist/scheduler/cron.js +60 -0
- package/dist/scheduler/cron.js.map +1 -1
- package/dist/system-capabilities.d.ts +11 -0
- package/dist/system-capabilities.d.ts.map +1 -0
- package/dist/system-capabilities.js +109 -0
- package/dist/system-capabilities.js.map +1 -0
- package/dist/types.d.ts +62 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/watcher/index.d.ts +2 -0
- package/dist/watcher/index.d.ts.map +1 -1
- package/dist/watcher/index.js +31 -1
- package/dist/watcher/index.js.map +1 -1
- package/dist/workspace-automations.d.ts +16 -0
- package/dist/workspace-automations.d.ts.map +1 -0
- package/dist/workspace-automations.js +133 -0
- package/dist/workspace-automations.js.map +1 -0
- package/package.json +19 -3
- package/registry/bluesky.md +12 -89
- package/registry/skills-registry.json +6 -0
- package/src/db/schema.sql +35 -0
- package/src/index.ts +159 -6
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ sulala onboard
|
|
|
25
25
|
sulala onboard --install-daemon
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
Then open http://127.0.0.1:3000 (dashboard and API).
|
|
28
|
+
Then open http://127.0.0.1:3000 (dashboard and API). **Default LLM is Ollama** (local, no API key). On first run, if Ollama is not installed, the app will start the official installer for your OS (Mac/Linux/Windows). You can optionally add API keys at http://127.0.0.1:3000/onboard to use OpenAI, Claude, Gemini, or OpenRouter instead.
|
|
29
29
|
|
|
30
30
|
**Or run from source (clone this repo):**
|
|
31
31
|
|
|
@@ -90,7 +90,7 @@ Set `GATEWAY_URL` and optionally `GATEWAY_API_KEY` when using gateway commands.
|
|
|
90
90
|
|
|
91
91
|
**AI models:** Supported providers: OpenAI (e.g. gpt-5.2, gpt-5-mini, gpt-4o-mini), OpenRouter (one key for many models, e.g. openai/gpt-5.2, anthropic/claude-sonnet-4), Claude (Opus 4.6, Sonnet 4.6, Haiku 4.5), Gemini (2.5 Flash/Pro, 3.1 Pro), Ollama (local). See [docs/MODELS.md](docs/MODELS.md) for model IDs and env vars (`AI_*_DEFAULT_MODEL`, `OPENROUTER_API_KEY`, `GOOGLE_GEMINI_API_KEY`).
|
|
92
92
|
|
|
93
|
-
**Config:** Watched folders can be set in `.env` (`WATCH_FOLDERS`) or in `config/watched.json` (array `folders`); both are merged. Optional gateway auth via `GATEWAY_API_KEY`; webhooks via `WEBHOOK_URL` or `WEBHOOK_URLS` and optional `WEBHOOK_SECRET`. AI:
|
|
93
|
+
**Config:** Watched folders can be set in `.env` (`WATCH_FOLDERS`) or in `config/watched.json` (array `folders`); both are merged. Optional gateway auth via `GATEWAY_API_KEY`; webhooks via `WEBHOOK_URL` or `WEBHOOK_URLS` and optional `WEBHOOK_SECRET`. AI: default is **Ollama** (no key; installs automatically if missing). Override with `AI_DEFAULT_PROVIDER=openai` (or `claude`, `gemini`, `openrouter`) and set the corresponding API keys. Optional: `OLLAMA_BASE_URL` (default `http://localhost:11434`). Optional rate limit: `RATE_LIMIT_MAX`, `RATE_LIMIT_WINDOW_MS`. See `.env.example`.
|
|
94
94
|
|
|
95
95
|
**Docker:** Build and run with dashboard served from the gateway:
|
|
96
96
|
```bash
|
|
@@ -133,3 +133,4 @@ sulala_agent/
|
|
|
133
133
|
## License
|
|
134
134
|
|
|
135
135
|
MIT
|
|
136
|
+
# agent
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: airtable
|
|
3
|
+
description: Use Airtable (bases, records) via the Portal. When the user asks about Airtable bases or records, list connections with list_integrations_connections (provider airtable) and use run_command with curl to the Airtable API or gateway.
|
|
4
|
+
metadata:
|
|
5
|
+
{
|
|
6
|
+
"sulala": {
|
|
7
|
+
"emoji": "📊",
|
|
8
|
+
"requires": { "bins": ["curl"] }
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Airtable
|
|
14
|
+
|
|
15
|
+
1. **list_integrations_connections** with `provider: "airtable"` → 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 `Content-Type: application/json`.
|
|
18
|
+
|
|
19
|
+
Airtable API uses **base ID** and **table name or ID**. Add `api.airtable.com` to **ALLOWED_CURL_HOSTS**. Official docs: https://airtable.com/developers/web/api/introduction
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Bases and tables
|
|
24
|
+
|
|
25
|
+
- **List bases**: `GET https://api.airtable.com/v0/meta/bases`. Returns `bases[].id`, `bases[].name`. Use base `id` in table URLs.
|
|
26
|
+
- **List tables** (schema) in a base: `GET https://api.airtable.com/v0/meta/bases/<baseId>/tables`. Returns table names and field definitions.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Records
|
|
31
|
+
|
|
32
|
+
- **List records** (one table): `GET https://api.airtable.com/v0/<baseId>/<tableNameOrId>?maxRecords=20`. Optional: `?filterByFormula=<formula>`, `?sort[0][field]=Name`. Returns `records[].id`, `records[].fields`.
|
|
33
|
+
- **Create record**: `POST https://api.airtable.com/v0/<baseId>/<tableNameOrId>` with body `{"fields": {"Name": "Value", "OtherField": "Value"}}`. Field names must match the table schema.
|
|
34
|
+
|
|
35
|
+
Requirements: **PORTAL_GATEWAY_URL**, **PORTAL_API_KEY**; user must connect Airtable in the Portal. Base must be shared with the connected account.
|
package/context/asana.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: asana
|
|
3
|
+
description: Use Asana (workspaces, projects, tasks) via the Portal. When the user asks about Asana tasks or projects, list connections with list_integrations_connections (provider asana) and use run_command with curl to the Asana API or gateway.
|
|
4
|
+
metadata:
|
|
5
|
+
{
|
|
6
|
+
"sulala": {
|
|
7
|
+
"emoji": "✅",
|
|
8
|
+
"requires": { "bins": ["curl"] }
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Asana
|
|
14
|
+
|
|
15
|
+
1. **list_integrations_connections** with `provider: "asana"` → 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 `Content-Type: application/json`.
|
|
18
|
+
|
|
19
|
+
Base URL: `https://app.asana.com/api/1.0`. Add `app.asana.com` to **ALLOWED_CURL_HOSTS**. Official docs: https://developers.asana.com/reference/rest-api-reference
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Workspaces and projects
|
|
24
|
+
|
|
25
|
+
- **List workspaces**: `GET https://app.asana.com/api/1.0/workspaces`. Returns `data[].gid`, `data[].name`.
|
|
26
|
+
- **List projects** (in workspace): `GET https://app.asana.com/api/1.0/workspaces/<workspace_gid>/projects`. Returns `data[].gid`, `data[].name`.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Tasks
|
|
31
|
+
|
|
32
|
+
- **List tasks** (in project): `GET https://app.asana.com/api/1.0/projects/<project_gid>/tasks?opt_fields=name,completed,due_on,notes`. Returns `data[].gid`, `data[].name`, etc. Pagination: `offset`, `limit`.
|
|
33
|
+
- **Create task**: `POST https://app.asana.com/api/1.0/tasks` with header `Content-Type: application/json` and body `{"data": {"name": "Task title", "projects": ["<project_gid>"]}}`. The `projects` value must be an array of one or more project GID **strings** from `GET /workspaces/<workspace_gid>/projects` (e.g. `["1213334948480995"]`). Do **not** send `workspace` when sending `projects`—the API will error. Optional in `data`: `"notes": "Description"`, `"due_on": "YYYY-MM-DD"`. Flow: (1) GET `/workspaces` → pick a workspace `gid`, (2) GET `/workspaces/<workspace_gid>/projects` → pick a project `gid`, (3) POST `/tasks` with `{"data": {"name": "fix bug today", "projects": ["<project_gid>"]}}`.
|
|
34
|
+
- **Get task**: `GET https://app.asana.com/api/1.0/tasks/<task_gid>?opt_fields=name,completed,notes,due_on,projects`.
|
|
35
|
+
- **Update task** (mark complete, etc.): `PUT https://app.asana.com/api/1.0/tasks/<task_gid>` with body `{"data": {"completed": true}}` or `{"data": {"name": "New name"}}`.
|
|
36
|
+
|
|
37
|
+
Requirements: **PORTAL_GATEWAY_URL**, **PORTAL_API_KEY**; user must connect Asana in the Portal.
|
package/context/bluesky.md
CHANGED
|
@@ -1,111 +1,46 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: bluesky
|
|
3
|
-
description: Post to Bluesky (AT Protocol). Use when the user asks to post
|
|
4
|
-
homepage: https://bsky.app
|
|
3
|
+
description: Post to Bluesky (AT Protocol). Use when the user asks to post to Bluesky or share content on Bluesky. Use either (A) Portal OAuth connection and the Bluesky proxy, or (B) app password from skill config (BSKY_HANDLE, BSKY_APP_PASSWORD).
|
|
5
4
|
metadata:
|
|
6
5
|
{
|
|
7
6
|
"sulala": {
|
|
8
7
|
"emoji": "🦋",
|
|
9
|
-
"requires": { "bins": ["curl"
|
|
10
|
-
"primaryEnv": "BSKY_APP_PASSWORD"
|
|
8
|
+
"requires": { "bins": ["curl"] }
|
|
11
9
|
}
|
|
12
10
|
}
|
|
13
11
|
---
|
|
14
12
|
|
|
15
|
-
# Bluesky
|
|
13
|
+
# Bluesky
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
Two ways to post:
|
|
18
16
|
|
|
19
|
-
|
|
17
|
+
## (A) Portal OAuth (recommended)
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
1. **list_integrations_connections** with `provider: "bluesky"` → get `connection_id`.
|
|
20
|
+
2. **bluesky_post** with that `connection_id` and the post text (max 300 characters).
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
Use the **bluesky_post** tool for posting. Do not use run_command (curl) for Bluesky—the correct endpoint is the portal gateway bsky-request, and the tool calls it for you.
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
- "Share [content] on Bluesky"
|
|
27
|
-
- "Post news from [URL] to Bluesky"
|
|
28
|
-
- "Post a headline about [topic]"
|
|
24
|
+
## (B) App password (skill config)
|
|
29
25
|
|
|
30
|
-
|
|
26
|
+
Set **BSKY_HANDLE** and **BSKY_APP_PASSWORD** (from bsky.app → Settings → App Passwords) in Skills → Bluesky config.
|
|
31
27
|
|
|
32
|
-
|
|
28
|
+
1. **Create session** to get access token:
|
|
29
|
+
```bash
|
|
30
|
+
curl -s -X POST "https://bsky.social/xrpc/com.atproto.server.createSession" \
|
|
31
|
+
-H "Content-Type: application/json" \
|
|
32
|
+
-d '{"identifier":"$BSKY_HANDLE","password":"$BSKY_APP_PASSWORD"}'
|
|
33
|
+
```
|
|
34
|
+
Response has `accessJwt` and `did`.
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
2. **Create post** (use the JWT and did from step 1):
|
|
37
|
+
```bash
|
|
38
|
+
curl -s -X POST "https://bsky.social/xrpc/com.atproto.repo.createRecord" \
|
|
39
|
+
-H "Content-Type: application/json" \
|
|
40
|
+
-H "Authorization: Bearer <accessJwt>" \
|
|
41
|
+
-d '{"repo":"<did>","collection":"app.bsky.feed.post","record":{"$type":"app.bsky.feed.post","text":"<post text>","createdAt":"<ISO8601>"}}'
|
|
42
|
+
```
|
|
38
43
|
|
|
39
|
-
|
|
44
|
+
Add `bsky.social` to **ALLOWED_CURL_HOSTS**. Optional: **BSKY_PDS** (default https://bsky.social) for a custom PDS.
|
|
40
45
|
|
|
41
|
-
|
|
42
|
-
CONFIG_PATH="${SULALA_CONFIG_PATH:-$HOME/.sulala/config.json}"
|
|
43
|
-
# If using workspace config, use: CONFIG_PATH=".sulala/config.json" (when run from project root)
|
|
44
|
-
BSKY_HANDLE=$(cat "$CONFIG_PATH" 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); e=d.get('skills',{}).get('entries',{}).get('bluesky',{}); print(e.get('handle','') or e.get('apiKey',''))" 2>/dev/null)
|
|
45
|
-
BSKY_APP_PASSWORD=$(cat "$CONFIG_PATH" 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); e=d.get('skills',{}).get('entries',{}).get('bluesky',{}); print(e.get('apiKey','') or e.get('password',''))" 2>/dev/null)
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
(Config may use `handle` + `apiKey` for Bluesky. Adjust field names if your config differs.)
|
|
49
|
-
|
|
50
|
-
### 2. Create session (login)
|
|
51
|
-
|
|
52
|
-
**Use `run_command` with `binary: "sh"` and `args: ["-c", "..."]`** so `$BSKY_HANDLE` and `$BSKY_APP_PASSWORD` expand from the environment. Do NOT call curl directly.
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
sh -c 'SESSION=$(curl -s -X POST "https://bsky.social/xrpc/com.atproto.server.createSession" -H "Content-Type: application/json" -d "{\"identifier\": \"$BSKY_HANDLE\", \"password\": \"$BSKY_APP_PASSWORD\"}"); echo "$SESSION"'
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
Then parse the JSON output with `python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('accessJwt',''), d.get('did',''))"` to get `ACCESS_TOKEN` and `DID`.
|
|
59
|
-
|
|
60
|
-
If the session response contains `"error"` or `"AuthenticationRequired"`, report: "Bluesky login failed. Check BSKY_HANDLE and BSKY_APP_PASSWORD in .env."
|
|
61
|
-
|
|
62
|
-
### 3. Create post
|
|
63
|
-
|
|
64
|
-
Text must be JSON-escaped. Use python to build the payload. **Use `run_command` with `binary: "sh"`** so `$ACCESS_TOKEN`, `$DID`, and other vars expand.
|
|
65
|
-
|
|
66
|
-
Run a single `sh -c` that chains steps 2 and 3 so SESSION, ACCESS_TOKEN, DID persist in the same shell:
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
|
-
sh -c '
|
|
70
|
-
SESSION=$(curl -s -X POST "https://bsky.social/xrpc/com.atproto.server.createSession" \
|
|
71
|
-
-H "Content-Type: application/json" \
|
|
72
|
-
-d "{\"identifier\": \"$BSKY_HANDLE\", \"password\": \"$BSKY_APP_PASSWORD\"}");
|
|
73
|
-
ACCESS_TOKEN=$(echo "$SESSION" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get(\"accessJwt\",\"\"))");
|
|
74
|
-
DID=$(echo "$SESSION" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get(\"did\",\"\"))");
|
|
75
|
-
if [ -z "$ACCESS_TOKEN" ]; then echo "Login failed"; exit 1; fi;
|
|
76
|
-
POST_TEXT="Your post content here";
|
|
77
|
-
POST_JSON=$(python3 -c "import sys,json; print(json.dumps(sys.argv[1]))" "$POST_TEXT");
|
|
78
|
-
NOW=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z");
|
|
79
|
-
curl -s -X POST "https://bsky.social/xrpc/com.atproto.repo.createRecord" \
|
|
80
|
-
-H "Authorization: Bearer $ACCESS_TOKEN" \
|
|
81
|
-
-H "Content-Type: application/json" \
|
|
82
|
-
-d "{\"repo\": \"$DID\", \"collection\": \"app.bsky.feed.post\", \"record\": {\"\\$type\": \"app.bsky.feed.post\", \"text\": $POST_JSON, \"createdAt\": \"$NOW\"}}"
|
|
83
|
-
'
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
Replace `Your post content here` with the user's post text.
|
|
87
|
-
|
|
88
|
-
If the response contains `"uri"`, the post succeeded. Otherwise report the error.
|
|
89
|
-
|
|
90
|
-
## Posting from a news source
|
|
91
|
-
|
|
92
|
-
When the user wants to post **news** or **content from a URL**:
|
|
93
|
-
|
|
94
|
-
1. **Fetch the content** (e.g. with `curl -s <URL>` or read a file).
|
|
95
|
-
2. **Summarize** if the content is long — Bluesky posts are max 300 characters. Write a short headline or summary.
|
|
96
|
-
3. **Post** using the flow above. Optionally append the source URL if it fits within 300 chars.
|
|
97
|
-
|
|
98
|
-
Example for "post news from https://example.com/article":
|
|
99
|
-
- Fetch the page, extract title/lead.
|
|
100
|
-
- Build post: "Headline here — https://example.com/article"
|
|
101
|
-
- Post via the createRecord flow above.
|
|
102
|
-
|
|
103
|
-
## Character limit
|
|
104
|
-
|
|
105
|
-
Bluesky posts are limited to 300 characters. If the user's text or your summary exceeds that, truncate or split into multiple posts (thread). For a thread, create each post separately; the API does not natively support threads — you would need to reference the previous post's URI if linking them (advanced).
|
|
106
|
-
|
|
107
|
-
## Notes
|
|
108
|
-
|
|
109
|
-
- Use an **app password**, not the main account password. Create at bsky.app → Settings → App Passwords.
|
|
110
|
-
- `bsky.social` is the default PDS; custom PDS users would need a different host.
|
|
111
|
-
- Do not embed real credentials in the skill or in replies — use env or config.
|
|
46
|
+
Official docs: https://docs.bsky.app/docs/api/atproto/
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: calendar
|
|
3
|
+
description: Use Google Calendar via the Portal. When the user asks to create a calendar event, add to calendar, list events, or check calendar, use this skill with list_integrations_connections (provider calendar) and run_command + curl. Do not use Apple Calendar, osascript, or local calendar apps—use Google Calendar via the Portal.
|
|
4
|
+
metadata:
|
|
5
|
+
{
|
|
6
|
+
"sulala": {
|
|
7
|
+
"emoji": "📅",
|
|
8
|
+
"requires": { "bins": ["curl"] }
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Google Calendar
|
|
14
|
+
|
|
15
|
+
Use **list_integrations_connections** with `provider: "calendar"`, then **get_connection_token** to get an OAuth token (do not curl the portal from run_command—use the tool). Then call Calendar with that token.
|
|
16
|
+
|
|
17
|
+
1. **list_integrations_connections** with `provider: "calendar"` → get `connection_id`.
|
|
18
|
+
2. **get_connection_token** with that `connection_id` → returns `accessToken` (runs server-side).
|
|
19
|
+
3. **run_command (curl)** — call Calendar APIs 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/calendar/v3`
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## List calendars
|
|
28
|
+
|
|
29
|
+
`GET https://www.googleapis.com/calendar/v3/users/me/calendarList`. Use `items[].id` (e.g. `primary`) for listing events.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## List events
|
|
34
|
+
|
|
35
|
+
`GET https://www.googleapis.com/calendar/v3/calendars/<calendarId>/events?timeMin=<ISO8601>&timeMax=<ISO8601>&maxResults=20&singleEvents=true`. `calendarId` is often `primary`.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Create event
|
|
40
|
+
|
|
41
|
+
**Use this for all "create a calendar event" or "add to calendar" requests.**
|
|
42
|
+
|
|
43
|
+
`POST https://www.googleapis.com/calendar/v3/calendars/primary/events` with `Content-Type: application/json`, body:
|
|
44
|
+
|
|
45
|
+
`{"summary": "Event title", "description": "Optional description", "start": {"dateTime": "2025-03-04T21:00:00", "timeZone": "America/New_York"}, "end": {"dateTime": "2025-03-04T21:30:00", "timeZone": "America/New_York"}}`
|
|
46
|
+
|
|
47
|
+
- Example for "gym at 9 PM" today: use today's date, start 21:00, end 21:30 (or 22:00) in the user's timezone.
|
|
48
|
+
- All-day: use `"start": {"date": "2025-03-15"}`, `"end": {"date": "2025-03-16"}`.
|
|
49
|
+
- Times in ISO8601 with timezone (e.g. `America/New_York` or `UTC`).
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Update event
|
|
54
|
+
|
|
55
|
+
`PUT https://www.googleapis.com/calendar/v3/calendars/<calendarId>/events/<eventId>` with same body shape as create.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Delete event
|
|
60
|
+
|
|
61
|
+
`DELETE https://www.googleapis.com/calendar/v3/calendars/<calendarId>/events/<eventId>`.
|
|
62
|
+
|
|
63
|
+
Requirements: **PORTAL_GATEWAY_URL**, **PORTAL_API_KEY**; user must connect Google Calendar in the Portal or dashboard Integrations.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: country-info
|
|
3
|
+
description: Provides small info about various countries. Use when the user asks for information about a specific country.
|
|
4
|
+
---
|
|
5
|
+
# Country Info
|
|
6
|
+
|
|
7
|
+
Use when the user asks for small information about a country, such as facts, culture, and geography.
|
|
8
|
+
|
|
9
|
+
## How to use
|
|
10
|
+
- Ask for specific details about a country (e.g., "Tell me about Canada").
|
|
11
|
+
|
|
12
|
+
## Limits
|
|
13
|
+
- The information provided is brief and may not cover all aspects of the country.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: create-skill
|
|
3
|
+
description: Create a new Sulala skill when the user asks for one. Use write_file to create the .md file. Use when the user asks to create, add, or write a skill.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Create Sulala Skill
|
|
7
|
+
|
|
8
|
+
When the user asks to create a skill (e.g. "create a skill for X", "add a skill that does Y"), create it using **write_file**. Infer the skill from the request; ask only if critical details are missing.
|
|
9
|
+
|
|
10
|
+
## Skill format
|
|
11
|
+
|
|
12
|
+
Every Sulala skill is a `.md` file with YAML frontmatter and a markdown body:
|
|
13
|
+
|
|
14
|
+
```markdown
|
|
15
|
+
---
|
|
16
|
+
name: slug-or-name
|
|
17
|
+
description: One-line summary of when to use this skill. Include trigger terms.
|
|
18
|
+
---
|
|
19
|
+
# Skill Title
|
|
20
|
+
|
|
21
|
+
Use when the user asks for X, Y, or Z.
|
|
22
|
+
|
|
23
|
+
## How to use
|
|
24
|
+
- Commands, steps, or run_command examples
|
|
25
|
+
## Limits
|
|
26
|
+
- What not to do
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Required frontmatter:** `name` (slug, lowercase-hyphens), `description` (third person, includes WHAT and WHEN).
|
|
30
|
+
|
|
31
|
+
**Required when the skill uses run_command or external APIs:** `metadata` with `sulala.requires`:
|
|
32
|
+
- **bins** — list of CLI tools (e.g. `["curl", "jq"]`). Add these to ALLOWED_BINARIES. Always include if the skill uses run_command.
|
|
33
|
+
- **env** — list of required env var names for API keys (e.g. `["TMDB_ACCESS_TOKEN"]`). Users configure these in Skills config.
|
|
34
|
+
|
|
35
|
+
```yaml
|
|
36
|
+
metadata:
|
|
37
|
+
{
|
|
38
|
+
"sulala": {
|
|
39
|
+
"requires": {
|
|
40
|
+
"bins": ["curl"],
|
|
41
|
+
"env": ["TMDB_ACCESS_TOKEN"]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Where to write
|
|
48
|
+
|
|
49
|
+
Use the **SKILL.md flow** (direct skill directory, not packaged .skill):
|
|
50
|
+
|
|
51
|
+
1. **Use the path from the Workspace section:** In "## Context" → **## Workspace**, the prompt gives **Your skill output directory**. Use that path with write_file: `<that-directory>/<slug>/SKILL.md`. It is already resolved for the current OS. Do **not** use `~` or `$HOME`; the tool does not expand them and would create a literal folder under the project.
|
|
52
|
+
2. Add `SKILL.md` with YAML frontmatter (`name`, `description`) and instructions.
|
|
53
|
+
3. Add any `scripts/`, `references/`, or `assets/` under that folder if needed.
|
|
54
|
+
4. Do **not** create skills under the project folder (e.g. `context/`) — they will be overwritten on project updates.
|
|
55
|
+
|
|
56
|
+
If write_file cannot write to the path from the Workspace section (e.g. permission or workspace root restriction), tell the user: "Create the skill at [that path], then refresh skills or restart the gateway."
|
|
57
|
+
|
|
58
|
+
## Steps
|
|
59
|
+
|
|
60
|
+
1. Infer purpose and name from the user's request.
|
|
61
|
+
2. Draft frontmatter (name, description) and body (when to use, how to use, limits).
|
|
62
|
+
3. Call **write_file** with `path` = the skill output directory from **## Workspace** + `/<slug>/SKILL.md` (e.g. `/Users/you/.sulala/workspace/skills/<slug>/SKILL.md`). Create the directory if needed.
|
|
63
|
+
4. Confirm: "Created skill at [path]. Refresh skills or restart the gateway to load it."
|
|
64
|
+
|
|
65
|
+
## Example (API skill with metadata)
|
|
66
|
+
|
|
67
|
+
User: "Create a skill that fetches movie data from TMDB"
|
|
68
|
+
|
|
69
|
+
Include **metadata** with `bins` (curl) and `env` (API key). Use write_file with the path from **## Workspace** + `/fetch-movie/SKILL.md`:
|
|
70
|
+
|
|
71
|
+
```markdown
|
|
72
|
+
---
|
|
73
|
+
name: fetch-movie
|
|
74
|
+
description: Fetch movies using TMDB API via curl. Use when the user asks for movie details or to search movies by title.
|
|
75
|
+
metadata:
|
|
76
|
+
{
|
|
77
|
+
"sulala": {
|
|
78
|
+
"requires": {
|
|
79
|
+
"bins": ["curl"],
|
|
80
|
+
"env": ["TMDB_ACCESS_TOKEN"]
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
# Fetch Movie
|
|
87
|
+
|
|
88
|
+
Use **run_command** with `curl` to get movie data from The Movie Database (TMDB) API. Add `curl` to ALLOWED_BINARIES. Store your TMDB API token in Skills config as `TMDB_ACCESS_TOKEN`.
|
|
89
|
+
|
|
90
|
+
## TMDB Search API
|
|
91
|
+
|
|
92
|
+
- **Search:** `curl -s -H "Authorization: Bearer $TMDB_ACCESS_TOKEN" "https://api.themoviedb.org/3/search/movie?query=QUERY"`
|
|
93
|
+
|
|
94
|
+
## Limits
|
|
95
|
+
|
|
96
|
+
- Do not expose the token in responses. Use the env var in run_command.
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Example (stock price)
|
|
100
|
+
|
|
101
|
+
User: "Create a skill that fetches stock prices"
|
|
102
|
+
|
|
103
|
+
Use write_file with the path from **## Workspace** + `/stock-price/SKILL.md`:
|
|
104
|
+
|
|
105
|
+
```markdown
|
|
106
|
+
---
|
|
107
|
+
name: stock-price
|
|
108
|
+
description: Fetches stock prices via API. Use when the user asks for stock quotes, share price, or market data.
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
# Stock Price
|
|
112
|
+
|
|
113
|
+
Use **run_command** with `curl` to call a stock API. Add `curl` to ALLOWED_BINARIES.
|
|
114
|
+
|
|
115
|
+
## Alpha Vantage (requires API key)
|
|
116
|
+
|
|
117
|
+
curl -s "https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=AAPL&apikey=$ALPHA_VANTAGE_API_KEY"
|
|
118
|
+
|
|
119
|
+
## Limits
|
|
120
|
+
|
|
121
|
+
- Do not share API keys in responses.
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Tips
|
|
125
|
+
|
|
126
|
+
- Description: third person, specific, include trigger terms. Example: "Fetches weather for a city. Use when the user asks for weather, temperature, or forecast."
|
|
127
|
+
- Body: concise; if the skill uses run_command, say which binaries and add to ALLOWED_BINARIES.
|
|
128
|
+
- Slug: lowercase, hyphens only (e.g. `my-new-skill`).
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: discord
|
|
3
|
+
description: Use Discord (servers, channels, send messages) via Settings → Channels. Do not use list_integrations_connections for Discord—it only lists OAuth apps. Use discord_list_guilds, discord_list_channels, and discord_send_message (token from Settings → Channels).
|
|
4
|
+
metadata:
|
|
5
|
+
{
|
|
6
|
+
"sulala": {
|
|
7
|
+
"emoji": "🎮",
|
|
8
|
+
"requires": { "env": ["DISCORD_BOT_TOKEN"] }
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Discord
|
|
14
|
+
|
|
15
|
+
Discord is configured in **Settings → Channels (Discord)** or via **DISCORD_BOT_TOKEN** in the agent env. **Do not call list_integrations_connections for Discord**—that tool only returns OAuth connections (Gmail, Slack, etc.). Discord uses a bot token, not OAuth.
|
|
16
|
+
|
|
17
|
+
Use the dedicated tools (they use the token from Settings):
|
|
18
|
+
- **discord_list_guilds** — list servers (guilds) the bot is in. Returns guild id and name.
|
|
19
|
+
- **discord_list_channels** — list channels in a guild. Requires guild_id from discord_list_guilds. Returns channel id, name, type (0=text, 2=voice, 4=category).
|
|
20
|
+
- **discord_send_message** — send a message to a channel. Requires channel_id and content (max 2000 chars).
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Flow
|
|
25
|
+
|
|
26
|
+
1. **discord_list_guilds** — get server (guild) ids and names.
|
|
27
|
+
2. **discord_list_channels** with `guild_id` — get channel ids (type 0 = text channel).
|
|
28
|
+
3. **discord_send_message** with `channel_id` and `content` — send the message.
|
|
29
|
+
|
|
30
|
+
Requirements: Bot must be added to the server and have permissions to read channels and send messages.
|
package/context/docs.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: docs
|
|
3
|
+
description: Use Google Docs via the Portal. When the user asks to list, create, or read Google Docs, use this skill with list_integrations_connections (provider docs) and run_command + curl.
|
|
4
|
+
metadata:
|
|
5
|
+
{
|
|
6
|
+
"sulala": {
|
|
7
|
+
"emoji": "📄",
|
|
8
|
+
"requires": { "bins": ["curl"] }
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Google Docs
|
|
14
|
+
|
|
15
|
+
Use **list_integrations_connections** with `provider: "docs"`, then **get_connection_token** (do not curl the portal). Then call the API with that token.
|
|
16
|
+
|
|
17
|
+
1. **list_integrations_connections** with `provider: "docs"` → 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` to **ALLOWED_CURL_HOSTS**.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## List / create / read
|
|
26
|
+
|
|
27
|
+
List and create Docs via the Drive API (mimeType `application/vnd.google-apps.document`). Export to read: `GET https://www.googleapis.com/drive/v3/files/<id>/export?mimeType=text/plain` with `Authorization: Bearer <accessToken>`.
|
|
28
|
+
|
|
29
|
+
Requirements: **PORTAL_GATEWAY_URL**, **PORTAL_API_KEY**; user must connect Google Docs in the Portal or dashboard Integrations.
|
package/context/drive.md
ADDED
|
@@ -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`.
|