@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
package/README.md CHANGED
@@ -2,18 +2,27 @@
2
2
 
3
3
  A local-first platform that combines **file monitoring**, **task scheduling**, **AI orchestration**, and a **plugin system**. Runs on `127.0.0.1` and stays on your machine.
4
4
 
5
+ ## Contents
6
+
7
+ - [Architecture](#architecture-high-level)
8
+ - [Quick start](#quick-start)
9
+ - [Hub & integrations](#hub--integrations)
10
+ - [Requirements](#requirements)
11
+ - [Project layout](#project-layout)
12
+ - [Security](#security)
13
+
5
14
  ## Architecture (high level)
6
15
 
7
16
  | Layer | Role |
8
17
  |-------|------|
9
- | **Gateway** | REST + WebSocket API on `localhost:3000`; auth and request handling |
18
+ | **Gateway** | REST + WebSocket API on `localhost:2026`; auth and request handling |
10
19
  | **File watcher** | Real-time folder watch (add/change/delete) → event triggers |
11
20
  | **Task scheduler** | Cron-like scheduling + queue with retries and failure handling |
12
21
  | **AI orchestration** | Single interface to multiple providers (OpenAI, OpenRouter, Claude, Gemini, Ollama); routing and rate limits |
13
22
  | **Plugins** | Scripts and integrations that hook into events, tasks, and AI |
14
23
  | **Persistence** | SQLite for tasks, file state, logs, and AI results |
15
24
 
16
- See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) and the project diagram for full layout.
25
+ See [docs/architecture.md](docs/architecture.md) and the project diagram for full layout.
17
26
 
18
27
  ## Quick start
19
28
 
@@ -25,7 +34,7 @@ sulala onboard
25
34
  sulala onboard --install-daemon
26
35
  ```
27
36
 
28
- Then open http://127.0.0.1:3000 (dashboard and API). Add API keys at http://127.0.0.1:3000/onboard if needed.
37
+ Then open http://127.0.0.1:2026 (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:2026/onboard to use OpenAI, Claude, Gemini, or OpenRouter instead.
29
38
 
30
39
  **Or run from source (clone this repo):**
31
40
 
@@ -40,21 +49,12 @@ cp .env.example .env
40
49
  npm start
41
50
  ```
42
51
 
43
- **Development:** `npm run dev` runs the app with `tsx watch` (restarts on file changes). For a production build: `npm run build` then `node dist/index.js` (or run the gateway with `node dist/gateway/server.js`). Run tests with `npm test` and lint with `npm run lint`.
52
+ **Development:** `npm run dev` runs with `tsx watch` (restarts on file changes). Production: `npm run build` then `node dist/index.js`. Tests: `npm test`; lint: `npm run lint`.
44
53
 
45
54
  **Dashboard (React + Vite + shadcn/ui):**
46
55
 
47
- - **Dev:** run the UI on its own port (e.g. 5173) while the gateway runs on 3000:
48
- ```bash
49
- npm run dashboard
50
- ```
51
- Then open the URL Vite prints (e.g. http://localhost:5173). Set `VITE_GATEWAY_URL=http://127.0.0.1:3000` in `dashboard/.env` if the API is on another host.
52
- - **Production:** build and serve from the gateway (one origin):
53
- ```bash
54
- npm run dashboard:build
55
- npm start
56
- ```
57
- Then open http://127.0.0.1:3000 — the gateway serves the built dashboard and the API.
56
+ - **Dev:** `npm run dashboard` UI on its own port (e.g. 5173); gateway on 2026. Set `VITE_GATEWAY_URL=http://127.0.0.1:2026` in `dashboard/.env` if needed.
57
+ - **Production:** `npm run dashboard:build` then `npm start` — gateway serves the built dashboard and API at http://127.0.0.1:2026.
58
58
 
59
59
  **CLI** (from project root):
60
60
 
@@ -74,29 +74,44 @@ sulala skill update
74
74
  sulala skill uninstall apple-notes [--global]
75
75
  ```
76
76
 
77
- **Skill commands:** `sulala skill list` lists registry skills. `sulala skill install <slug> [--global]` installs to workspace (default) or managed dir (`~/.sulala/skills`). `sulala skill update` refreshes installed skills from the registry. `sulala skill uninstall <slug> [--global]`. `sulala init [dir]` creates config/context/registry and copies `.env.example` → `.env` in a directory.
77
+ **Skill commands:**
78
+
79
+ - `sulala skill list` — list registry skills
80
+ - `sulala skill install <slug> [--global]` — install to workspace (default) or `~/.sulala/skills`
81
+ - `sulala skill update` — refresh installed skills from the registry
82
+ - `sulala skill uninstall <slug> [--global]` — remove a skill
83
+ - `sulala init [dir]` — create config/context/registry and copy `.env.example` → `.env`
84
+
85
+ **Onboard & daemon (global install):** Run `sulala onboard` to create `~/.sulala` and a default `.env`; the browser opens to **http://127.0.0.1:2026/onboard** to add API keys (saved to `~/.sulala/.env`). Run `sulala onboard --install-daemon` to install a background service (launchd on macOS, systemd on Linux) so the agent runs at login. Logs: `~/.sulala/logs/`. Use `sulala stop` / `sulala start` to stop or start the daemon; `sulala onboard --uninstall-daemon` to remove it.
78
86
 
79
- **Onboard & daemon (global install):** After `npm i -g @sulala/agent`, run `sulala onboard` to create `~/.sulala` and a default `.env`; your browser opens to **http://127.0.0.1:3000/onboard** where you can add API keys (saved to `~/.sulala/.env`). Then `sulala onboard --install-daemon` to install a background service (launchd on macOS, systemd on Linux) so the agent runs at login; the browser opens again after the daemon starts. Logs: `~/.sulala/logs/`. **Stop/start:** `sulala stop` stops the agent daemon; `sulala start` starts it again. To remove the daemon: `sulala onboard --uninstall-daemon`.
87
+ **Works on any device:** The published package includes the default skills registry and bundled skills (`registry/`, `context/`), so `sulala skill list` and the agent work out of the box. Optionally set `SKILLS_REGISTRY_URL` for a remote skills store.
80
88
 
81
- **Works on any device:** The published package includes the default skills registry and bundled skills (`registry/`, `context/`), so `sulala skill list` and the agent work out of the box without setting `SKILLS_REGISTRY_URL`. Optional: point `SKILLS_REGISTRY_URL` at a remote store for more skills.
89
+ **Hub & integrations**
82
90
 
83
- Set `GATEWAY_URL` and optionally `GATEWAY_API_KEY` when using gateway commands. Set `SULALA_SKILLS_DIR` and `AGENT_CONTEXT_PATH` for skill paths.
91
+ - **Skills hub:** [hub.sulala.ai](https://hub.sulala.ai) — upload skills for the agent and share them with others; install via `sulala skill list` / `sulala skill install` when using that registry.
92
+ - **Integrations:** Add more integrations (Gmail, Slack, GitHub, Calendar, etc.) for the agent; use the dashboard Integrations area or the integrations catalog to connect and manage them.
93
+ - **Portal (testing):** [portal.sulala.ai](https://portal.sulala.ai) — connect OAuth apps for testing (Gmail, Calendar, Slack, GitHub, etc.). Set `PORTAL_GATEWAY_URL` and `PORTAL_API_KEY` in the agent (e.g. in dashboard Settings → Portal) so it can list connections and get tokens. See [docs/sulalahub.md](docs/sulalahub.md) and `context/portal-integrations.md`.
84
94
 
85
- **Agent runner:** Multi-turn sessions with optional tool use. Create a session, then send messages to run the agent loop (model can call tools; built-in `run_task` enqueues background tasks). See [docs/ROADMAP_AGENT_RUNNER.md](docs/ROADMAP_AGENT_RUNNER.md) and [docs/AGENT_API.md](docs/AGENT_API.md).
86
- - `GET /api/agent/sessions` — list sessions (query: `limit`).
87
- - `POST /api/agent/sessions` create or get session (body: `{ "session_key": "my-chat", "meta": {} }`).
88
- - `GET /api/agent/sessions/:id` — get session and message history.
95
+ **Env for CLI/gateway:** Set `GATEWAY_URL` and optionally `GATEWAY_API_KEY` for gateway commands; `SULALA_SKILLS_DIR` and `AGENT_CONTEXT_PATH` for skill paths.
96
+
97
+ **Agent runner:** Multi-turn sessions with optional tool use. See [docs/roadmap-agent-runner.md](docs/roadmap-agent-runner.md) and [docs/agent-api.md](docs/agent-api.md).
98
+
99
+ - `GET /api/agent/sessions` — list sessions (query: `limit`)
100
+ - `POST /api/agent/sessions` — create or get session (body: `{ "session_key": "my-chat", "meta": {} }`)
101
+ - `GET /api/agent/sessions/:id` — get session and message history
89
102
  - `POST /api/agent/sessions/:id/messages` — send a message and run the agent turn (body: `{ "message": "…", "system_prompt": "…", "provider": "openai", "timeout_ms": 300000 }`). Returns `{ finalContent, messages, turnCount }`. Runs are serialized per session; client disconnect or timeout aborts the run (`AGENT_TIMEOUT_MS` in env or `timeout_ms` in body).
90
103
 
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`).
104
+ **AI models:** OpenAI, OpenRouter, Claude, Gemini, Ollama (local). See [docs/models.md](docs/models.md) for model IDs and env vars.
92
105
 
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: set `OPENAI_API_KEY`, `OPENROUTER_API_KEY`, `ANTHROPIC_API_KEY`, and/or use Ollama (`OLLAMA_BASE_URL`, default `http://localhost:11434`). Optional rate limit: `RATE_LIMIT_MAX`, `RATE_LIMIT_WINDOW_MS`. See `.env.example`.
106
+ **Config:** Watched folders: `.env` (`WATCH_FOLDERS`) or `config/watched.json` (array `folders`); both are merged. Gateway auth: `GATEWAY_API_KEY`; webhooks: `WEBHOOK_URL` or `WEBHOOK_URLS` (optional `WEBHOOK_SECRET`). Default AI 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`, `RATE_LIMIT_MAX`, `RATE_LIMIT_WINDOW_MS`. See `.env.example` and [docs/config.md](docs/config.md).
107
+
108
+ **Docker:** Dashboard is served from the gateway.
94
109
 
95
- **Docker:** Build and run with dashboard served from the gateway:
96
110
  ```bash
97
111
  docker compose up --build
98
112
  ```
99
- Then open http://localhost:3000. Mount `./config` for watched folders; data is stored in a named volume.
113
+
114
+ Open http://localhost:2026. Mount `./config` for watched folders; data is in a named volume.
100
115
 
101
116
  ## Requirements
102
117
 
@@ -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.
@@ -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.
@@ -1,111 +1,46 @@
1
1
  ---
2
2
  name: bluesky
3
- description: Post to Bluesky (AT Protocol). Use when the user asks to post a tweet/thread to Bluesky, share content on Bluesky, or post news/headlines from a source (URL or text) to Bluesky.
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", "python3", "sh"], "env": ["BSKY_HANDLE", "BSKY_APP_PASSWORD"] },
10
- "primaryEnv": "BSKY_APP_PASSWORD"
8
+ "requires": { "bins": ["curl"] }
11
9
  }
12
10
  }
13
11
  ---
14
12
 
15
- # Bluesky Posting
13
+ # Bluesky
16
14
 
17
- Post to Bluesky via the AT Protocol. Use **run_command** with `curl`, `python3`, and `sh`. Add `curl`, `python3`, and `sh` to ALLOWED_BINARIES.
15
+ Two ways to post:
18
16
 
19
- Requires `BSKY_HANDLE` (e.g. `your.bsky.social`) and `BSKY_APP_PASSWORD` (app password from bsky.app → Settings → App Passwords). Set them in `.env` or in the skill config (`~/.sulala/config.json` or `.sulala/config.json` in the project; see Skill config in the dashboard). Config is re-read when the file changes; enable/disable takes effect without restart.
17
+ ## (A) Portal OAuth (recommended)
20
18
 
21
- **IMPORTANT:** `run_command` does not run a shell — `$BSKY_HANDLE` and `$BSKY_APP_PASSWORD` will not expand if you call curl directly. Always use `binary: "sh"` and `args: ["-c", "curl ... $BSKY_HANDLE ... $BSKY_APP_PASSWORD ..."]` so the env vars expand.
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
- ## When to Use
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
- - "Post this to Bluesky"
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
- ## Post Flow (non-interactive)
26
+ Set **BSKY_HANDLE** and **BSKY_APP_PASSWORD** (from bsky.app → Settings → App Passwords) in Skills → Bluesky config.
31
27
 
32
- ### 1. Ensure credentials
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
- BSKY_HANDLE="${BSKY_HANDLE:?Set BSKY_HANDLE}"
36
- BSKY_APP_PASSWORD="${BSKY_APP_PASSWORD:?Set BSKY_APP_PASSWORD}"
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
- If empty, read from Sulala config:
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.
@@ -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.