sparkecoder 0.1.125 → 0.1.127
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/dist/agent/index.d.ts +3 -3
- package/dist/agent/index.js +52 -3
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +118 -17
- package/dist/cli.js.map +1 -1
- package/dist/db/index.d.ts +2 -2
- package/dist/{index-DczYH89U.d.ts → index-Bcz0aCAR.d.ts} +104 -104
- package/dist/index.d.ts +5 -5
- package/dist/index.js +118 -17
- package/dist/index.js.map +1 -1
- package/dist/{schema-DxrKyetI.d.ts → schema-BWbWmfDQ.d.ts} +3 -3
- package/dist/{search-CVVfuBPZ.d.ts → search-DOzC4ojH.d.ts} +4 -4
- package/dist/server/index.js +118 -17
- package/dist/server/index.js.map +1 -1
- package/dist/skills/default/slack-messaging.md +268 -0
- package/dist/tools/index.d.ts +3 -3
- package/dist/tools/index.js +3 -1
- package/dist/tools/index.js.map +1 -1
- package/package.json +1 -1
- package/src/skills/default/slack-messaging.md +268 -0
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/build-manifest.json +2 -2
- package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
- package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.html +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.html +1 -1
- package/web/.next/standalone/web/.next/server/app/index.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.html +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
- package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
- /package/web/.next/standalone/web/.next/static/{qtMOCCjmqN22PUb49g4j- → l63ZK5juWEj8lzzuVVSpx}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{qtMOCCjmqN22PUb49g4j- → l63ZK5juWEj8lzzuVVSpx}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{qtMOCCjmqN22PUb49g4j- → l63ZK5juWEj8lzzuVVSpx}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{qtMOCCjmqN22PUb49g4j- → l63ZK5juWEj8lzzuVVSpx}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{qtMOCCjmqN22PUb49g4j- → l63ZK5juWEj8lzzuVVSpx}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/static/{qtMOCCjmqN22PUb49g4j- → l63ZK5juWEj8lzzuVVSpx}/_ssgManifest.js +0 -0
- /package/web/.next/static/{qtMOCCjmqN22PUb49g4j- → l63ZK5juWEj8lzzuVVSpx}/_buildManifest.js +0 -0
- /package/web/.next/static/{qtMOCCjmqN22PUb49g4j- → l63ZK5juWEj8lzzuVVSpx}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{qtMOCCjmqN22PUb49g4j- → l63ZK5juWEj8lzzuVVSpx}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Slack Messaging
|
|
3
|
+
description: Send Slack DMs to a user by email, or post to a specific channel by name (not just by ID). Wraps Slack's lookup APIs around the messenger tool so you don't need to know cryptic IDs ahead of time.
|
|
4
|
+
platforms: ["darwin", "linux"]
|
|
5
|
+
version: 1
|
|
6
|
+
lastUpdated: "2026-05-22"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Slack Messaging Skill
|
|
10
|
+
|
|
11
|
+
Most of the time you already have the right Slack `channel`/`thread_ts` from
|
|
12
|
+
the inbound event pill (`[SLACK channel=C0B5... thread=1779... user=…]`) — in
|
|
13
|
+
that case **just call `messenger({channel:'slack', to:<channelId>, threadTs:<ts>, text:…})`** and you're done.
|
|
14
|
+
|
|
15
|
+
This skill is for the cases the inbound pill **doesn't** give you:
|
|
16
|
+
|
|
17
|
+
1. **DM a specific person by email** (e.g. "DM @ryan@studyfetch.com when the build's done")
|
|
18
|
+
2. **Post to a named channel** when you only know its name (`#general`, not `C0123…`)
|
|
19
|
+
3. **Reply in the same channel you're currently in** when you don't have the ID handy (rare — but useful for proactive worker → channel updates)
|
|
20
|
+
|
|
21
|
+
## Architecture
|
|
22
|
+
|
|
23
|
+
`messenger` already does the *sending*. It accepts any Slack channel id
|
|
24
|
+
(`C…` for channels, `G…` for legacy private, `D…` for DMs, or even a user
|
|
25
|
+
id `U…` which Slack auto-routes to a DM). What this skill teaches is how to
|
|
26
|
+
**resolve a human-friendly identifier into the right id** using the bot's
|
|
27
|
+
own credentials, then hand it to `messenger`.
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
email / channel-name ──curl Slack API──▶ channel id ──messenger──▶ posted
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The bot token is in the app config — read it once and reuse it across the
|
|
34
|
+
turn. **Do not echo the token in any user-visible output, any log line, or
|
|
35
|
+
any Slack message you send.**
|
|
36
|
+
|
|
37
|
+
Config file location is OS-dependent. Use this resolver:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# OS-aware sparkecoder.config.json path
|
|
41
|
+
if [[ -f "$HOME/Library/Application Support/sparkecoder/sparkecoder.config.json" ]]; then
|
|
42
|
+
CONFIG="$HOME/Library/Application Support/sparkecoder/sparkecoder.config.json" # macOS
|
|
43
|
+
elif [[ -f "${XDG_DATA_HOME:-$HOME/.local/share}/sparkecoder/sparkecoder.config.json" ]]; then
|
|
44
|
+
CONFIG="${XDG_DATA_HOME:-$HOME/.local/share}/sparkecoder/sparkecoder.config.json" # Linux
|
|
45
|
+
elif [[ -f "$APPDATA/sparkecoder/sparkecoder.config.json" ]]; then
|
|
46
|
+
CONFIG="$APPDATA/sparkecoder/sparkecoder.config.json" # Windows (Git Bash)
|
|
47
|
+
else
|
|
48
|
+
echo "no sparkecoder config found" >&2; exit 1
|
|
49
|
+
fi
|
|
50
|
+
TOKEN=$(jq -r '.slack.botToken // empty' "$CONFIG")
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
If `$TOKEN` is empty, Slack isn't configured on this host — `task_failed`
|
|
54
|
+
with that as the reason instead of trying to mint a workaround.
|
|
55
|
+
|
|
56
|
+
## Required scopes
|
|
57
|
+
|
|
58
|
+
The bot needs these scopes added in the Slack app config. If a call fails
|
|
59
|
+
with `missing_scope`, that's the cause — surface the scope name to the user
|
|
60
|
+
so they can add it in the Slack dashboard:
|
|
61
|
+
|
|
62
|
+
| Use case | Scopes |
|
|
63
|
+
|---|---|
|
|
64
|
+
| Look up a user by email | `users:read`, `users:read.email` |
|
|
65
|
+
| Send a DM to that user | `chat:write` *(bots can DM any user in workspaces they're a member of)* |
|
|
66
|
+
| Look up a channel by name | `channels:read` (public), `groups:read` (private) |
|
|
67
|
+
| Post in a channel | `chat:write` + bot must be a member of that channel |
|
|
68
|
+
|
|
69
|
+
## Resolve an unknown user id → name + email
|
|
70
|
+
|
|
71
|
+
When you see a bare `U0123…` (or `<@U0123…>`) and you don't already have
|
|
72
|
+
the name/email from the channel pill or a previous lookup, call
|
|
73
|
+
`users.info`. The cache the host process maintains will also be warmed
|
|
74
|
+
by your call — so subsequent inbound messages from that person arrive
|
|
75
|
+
pre-enriched. Requires `users:read`; email field also needs
|
|
76
|
+
`users:read.email`.
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# id → { name, real_name, email }
|
|
80
|
+
INFO=$(curl -s -G "https://slack.com/api/users.info" \
|
|
81
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
82
|
+
--data-urlencode "user=U05C6RWBDPC")
|
|
83
|
+
echo "$INFO" | jq '{
|
|
84
|
+
ok,
|
|
85
|
+
name: (.user.profile.display_name_normalized // .user.profile.real_name // .user.name),
|
|
86
|
+
realName: .user.profile.real_name,
|
|
87
|
+
email: .user.profile.email,
|
|
88
|
+
isBot: .user.is_bot
|
|
89
|
+
}'
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Use this when:
|
|
93
|
+
- A workflow stamped a notification with someone's id but no name
|
|
94
|
+
- The orchestrator handed you a `<@U…>` token from a thread you weren't part of
|
|
95
|
+
- You want to verify an id is even a real user before pinging them
|
|
96
|
+
|
|
97
|
+
## Find someone by name (no email handy)
|
|
98
|
+
|
|
99
|
+
`users.list` returns every workspace member. Cheap one-time scan for
|
|
100
|
+
matching display name or real name. Cache the result for the rest of the
|
|
101
|
+
turn so you don't re-list:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
# Dump the directory once
|
|
105
|
+
USERS_JSON=$(curl -s "https://slack.com/api/users.list?limit=1000" \
|
|
106
|
+
-H "Authorization: Bearer $TOKEN")
|
|
107
|
+
|
|
108
|
+
# Find by exact or partial display/real name
|
|
109
|
+
echo "$USERS_JSON" | jq -r --arg q "ryan" '
|
|
110
|
+
.members
|
|
111
|
+
| map(select(.deleted | not))
|
|
112
|
+
| map(select(.is_bot | not))
|
|
113
|
+
| .[]
|
|
114
|
+
| select(
|
|
115
|
+
(.profile.display_name_normalized // "" | ascii_downcase | contains($q | ascii_downcase))
|
|
116
|
+
or (.profile.real_name // "" | ascii_downcase | contains($q | ascii_downcase))
|
|
117
|
+
)
|
|
118
|
+
| "\(.id)\t\(.profile.real_name // .name)\t\(.profile.email // "(no email)")"
|
|
119
|
+
'
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
If multiple matches come back, **ask the user** which one — don't pick.
|
|
123
|
+
|
|
124
|
+
## DM a user by their email
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
# 1. Resolve email → Slack user id (U…)
|
|
128
|
+
USER_ID=$(curl -s -G "https://slack.com/api/users.lookupByEmail" \
|
|
129
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
130
|
+
--data-urlencode "email=ryan@studyfetch.com" \
|
|
131
|
+
| jq -r 'if .ok then .user.id else "ERROR:" + .error end')
|
|
132
|
+
|
|
133
|
+
# If you get ERROR:users_not_found → the email isn't in this workspace.
|
|
134
|
+
# If you get ERROR:missing_scope → needs users:read.email.
|
|
135
|
+
|
|
136
|
+
[[ "$USER_ID" == ERROR:* ]] && echo "Slack lookup failed: ${USER_ID#ERROR:}" && exit 1
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Then hand the user id to messenger as the destination. Slack accepts a
|
|
140
|
+
user id where a channel id is expected and auto-opens the DM:
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
messenger({
|
|
144
|
+
action: 'post',
|
|
145
|
+
channel: 'slack',
|
|
146
|
+
to: '<U_FROM_ABOVE>',
|
|
147
|
+
text: 'Build done ✅ https://…'
|
|
148
|
+
})
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Both `chat.postMessage` and the messenger wrapper handle the DM open
|
|
152
|
+
internally — no separate `conversations.open` call needed.
|
|
153
|
+
|
|
154
|
+
### Multiple recipients
|
|
155
|
+
|
|
156
|
+
For broadcast, loop and post one DM per recipient — Slack has no built-in
|
|
157
|
+
"DM list" primitive. Limit to ≤10 in one turn to stay polite; do the rest
|
|
158
|
+
in a follow-up task if there are more.
|
|
159
|
+
|
|
160
|
+
## Post in a channel by name
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
# 1. Find the channel id from its name. types= covers public AND private
|
|
164
|
+
# channels the bot has been invited to. exclude_archived saves noise.
|
|
165
|
+
CHANNEL_ID=$(curl -s -G "https://slack.com/api/conversations.list" \
|
|
166
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
167
|
+
--data-urlencode "types=public_channel,private_channel" \
|
|
168
|
+
--data-urlencode "exclude_archived=true" \
|
|
169
|
+
--data-urlencode "limit=1000" \
|
|
170
|
+
| jq -r --arg name "general" '
|
|
171
|
+
if .ok then
|
|
172
|
+
(.channels[] | select(.name == $name) | .id) // ("ERROR:not_found:" + $name)
|
|
173
|
+
else
|
|
174
|
+
"ERROR:" + .error
|
|
175
|
+
end
|
|
176
|
+
')
|
|
177
|
+
|
|
178
|
+
[[ "$CHANNEL_ID" == ERROR:* ]] && echo "channel lookup failed: ${CHANNEL_ID#ERROR:}" && exit 1
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`#` prefix is **not** part of the actual name in the API — pass `general`,
|
|
182
|
+
not `#general`. The leading `#` is just UI sugar.
|
|
183
|
+
|
|
184
|
+
Then:
|
|
185
|
+
```
|
|
186
|
+
messenger({ action: 'post', channel: 'slack', to: '<CHANNEL_ID>', text: '…' })
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
If posting returns `not_in_channel`, the bot hasn't been invited. Tell the
|
|
190
|
+
user: "@invite the bot to `#<name>` then I'll retry."
|
|
191
|
+
|
|
192
|
+
## Reply in the channel you're currently in
|
|
193
|
+
|
|
194
|
+
If you're being driven by an inbound Slack event (the message you're
|
|
195
|
+
responding to was tagged `[SLACK channel=C0B5… thread=… user=…]`),
|
|
196
|
+
**always use that exact `channel` and `thread_ts`** — that keeps the
|
|
197
|
+
conversation threaded in the right place.
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
messenger({ action: 'post', channel: 'slack', to: 'C0B5CESSBGD', threadTs: '1779420529.312919', text: '…' })
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
If you genuinely don't have the channel id in scope (e.g. you're a worker
|
|
204
|
+
that wasn't spawned with a slack ref but you know the channel name), use
|
|
205
|
+
the lookup recipe above. **Do not** spam the channel from a worker
|
|
206
|
+
session unless the user explicitly asked — workers normally hand results
|
|
207
|
+
back to the orchestrator, which decides whether to post.
|
|
208
|
+
|
|
209
|
+
## Error catalogue (verbatim Slack codes)
|
|
210
|
+
|
|
211
|
+
| Slack `error` | What it means | What to do |
|
|
212
|
+
|---|---|---|
|
|
213
|
+
| `users_not_found` | No user with that email in this workspace | Stop. Tell the user the email isn't in the workspace. |
|
|
214
|
+
| `missing_scope` | Bot is missing a scope | Tell the user which scope; they add it and reinstall the app |
|
|
215
|
+
| `not_in_channel` | Bot isn't a member | Ask the user to invite the bot |
|
|
216
|
+
| `channel_not_found` | ID doesn't exist or isn't visible | Re-lookup; the channel may be archived |
|
|
217
|
+
| `invalid_auth` / `token_revoked` | Token is bad or revoked | Bail; tell user to refresh the bot token in settings |
|
|
218
|
+
| `rate_limited` | You're being throttled | Wait (`Retry-After` header, seconds), then retry once |
|
|
219
|
+
|
|
220
|
+
## Don'ts
|
|
221
|
+
|
|
222
|
+
- **Don't** put the bot token in any output, log line, or Slack message you
|
|
223
|
+
send. Read it from config, use it in curl/messenger, never echo it.
|
|
224
|
+
- **Don't** call `conversations.list` more than once per task — it's slow
|
|
225
|
+
and rate-limited. Cache the result in a shell variable for the turn.
|
|
226
|
+
- **Don't** invent channel names or emails. Look them up; if the lookup
|
|
227
|
+
fails, say so instead of fabricating an ID.
|
|
228
|
+
- **Don't** loop messenger to bypass Slack throttling. If you hit
|
|
229
|
+
`rate_limited`, back off.
|
|
230
|
+
- **Don't** use this skill for the inbound-message reply case — the
|
|
231
|
+
channel/thread is already on the inbound pill, just use it directly.
|
|
232
|
+
|
|
233
|
+
## Quick recipes
|
|
234
|
+
|
|
235
|
+
> All recipes assume `$TOKEN` was set via the OS-aware resolver block above.
|
|
236
|
+
> Don't re-fetch the token each call.
|
|
237
|
+
|
|
238
|
+
**"DM Ryan when this finishes":**
|
|
239
|
+
```bash
|
|
240
|
+
USER_ID=$(curl -s -G "https://slack.com/api/users.lookupByEmail" \
|
|
241
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
242
|
+
--data-urlencode "email=ryan@studyfetch.com" | jq -r '.user.id // empty')
|
|
243
|
+
[[ -z "$USER_ID" ]] && echo "ryan not in workspace" && exit 1
|
|
244
|
+
# then: messenger({ action:'post', channel:'slack', to: USER_ID, text: '…' })
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**"Post the daily summary to #standup":**
|
|
248
|
+
```bash
|
|
249
|
+
CHANNEL_ID=$(curl -s -G "https://slack.com/api/conversations.list" \
|
|
250
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
251
|
+
--data-urlencode "types=public_channel,private_channel" \
|
|
252
|
+
--data-urlencode "limit=1000" \
|
|
253
|
+
| jq -r '.channels[] | select(.name=="standup") | .id')
|
|
254
|
+
[[ -z "$CHANNEL_ID" ]] && echo "no #standup channel visible to the bot" && exit 1
|
|
255
|
+
# then: messenger({ action:'post', channel:'slack', to: CHANNEL_ID, text: '…' })
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**"DM each engineer their open PRs":**
|
|
259
|
+
```bash
|
|
260
|
+
for email in alice@x.com bob@x.com carol@x.com; do
|
|
261
|
+
uid=$(curl -s -G "https://slack.com/api/users.lookupByEmail" \
|
|
262
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
263
|
+
--data-urlencode "email=$email" | jq -r '.user.id // empty')
|
|
264
|
+
[[ -z "$uid" ]] && echo "$email: not in workspace" && continue
|
|
265
|
+
echo "$email → $uid"
|
|
266
|
+
done
|
|
267
|
+
# then issue messenger calls per row
|
|
268
|
+
```
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as ai from 'ai';
|
|
2
2
|
import { ToolSet } from 'ai';
|
|
3
|
-
import { B as BashToolProgress, W as WriteFileProgress, S as SearchToolProgress } from '../search-
|
|
4
|
-
export { b as BashToolOptions, d as SearchToolOptions, e as WriteFileToolOptions, c as createBashTool, f as createSearchTool, a as createWriteFileTool } from '../search-
|
|
3
|
+
import { B as BashToolProgress, W as WriteFileProgress, S as SearchToolProgress } from '../search-DOzC4ojH.js';
|
|
4
|
+
export { b as BashToolOptions, d as SearchToolOptions, e as WriteFileToolOptions, c as createBashTool, f as createSearchTool, a as createWriteFileTool } from '../search-DOzC4ojH.js';
|
|
5
5
|
|
|
6
6
|
interface TaskCompletionSignal {
|
|
7
7
|
status: 'completed' | 'failed';
|
|
@@ -110,7 +110,7 @@ interface TodoToolOptions {
|
|
|
110
110
|
workingDirectory: string;
|
|
111
111
|
}
|
|
112
112
|
declare function createTodoTool(options: TodoToolOptions): ai.Tool<{
|
|
113
|
-
action: "
|
|
113
|
+
action: "list" | "add" | "mark" | "clear" | "save_plan" | "list_plans" | "get_plan" | "delete_plan";
|
|
114
114
|
status?: "completed" | "pending" | "in_progress" | "cancelled" | undefined;
|
|
115
115
|
items?: {
|
|
116
116
|
content: string;
|
package/dist/tools/index.js
CHANGED
|
@@ -3374,8 +3374,10 @@ async function loadAllSkills(directories) {
|
|
|
3374
3374
|
}
|
|
3375
3375
|
async function loadSkillContent(skillName, directories) {
|
|
3376
3376
|
const allSkills = await loadAllSkills(directories);
|
|
3377
|
+
const normalize2 = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
|
|
3378
|
+
const wanted = normalize2(skillName);
|
|
3377
3379
|
const skill = allSkills.find(
|
|
3378
|
-
(s) => s.name
|
|
3380
|
+
(s) => normalize2(s.name) === wanted || normalize2(basename(s.filePath, extname4(s.filePath))) === wanted
|
|
3379
3381
|
);
|
|
3380
3382
|
if (!skill) {
|
|
3381
3383
|
return null;
|