slack-term 1.23.1 → 1.25.0
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 +108 -7
- package/SKILL.md +76 -5
- package/dist/cli.js +92 -89
- package/package.json +3 -1
- package/ts/cache.ts +120 -0
- package/ts/cli.ts +586 -34
- package/ts/format.ts +13 -10
- package/ts/slack.ts +63 -2
- package/ts/tail.ts +11 -7
- package/ts/todo.ts +483 -0
package/README.md
CHANGED
|
@@ -54,13 +54,23 @@ slack news --limit 5
|
|
|
54
54
|
# Recent messages across joined channels
|
|
55
55
|
slack msgs
|
|
56
56
|
|
|
57
|
+
# Channel/DM history. Thread parents are marked `[+N replies]`, so a line with
|
|
58
|
+
# replies is distinguishable from one without — open it with `slack thread`.
|
|
59
|
+
# --json adds reply_count / reply_users_count / latest_reply.
|
|
60
|
+
slack read "#general"
|
|
61
|
+
slack read "#general" --unreplied # only threads/posts whose last word isn't yours
|
|
62
|
+
|
|
57
63
|
# Search messages
|
|
58
64
|
slack search "deploy"
|
|
59
65
|
slack search "deploy" --count 50
|
|
60
66
|
|
|
61
67
|
# Send a message (two-step confirm — quote #channel)
|
|
62
68
|
slack send "#general" "Hello team"
|
|
63
|
-
# Prints
|
|
69
|
+
# Prints who you're acting as (From: @handle (Uxxxx) — Workspace) + a destination
|
|
70
|
+
# preview + confirm code; rerun with --code=<code> to actually send. Every gated write
|
|
71
|
+
# (send/edit/delete/upload/schedule/channel create/drafts) shows that From: line and
|
|
72
|
+
# binds it into the code, so a profile switch mid-flow invalidates it instead of acting
|
|
73
|
+
# as the wrong account.
|
|
64
74
|
slack send "#general" "Hello team" --code=<code>
|
|
65
75
|
|
|
66
76
|
# Reply in a thread — #chan:<thread_ts>, or just paste a message permalink
|
|
@@ -71,7 +81,7 @@ slack send "https://acme.slack.com/archives/C0123456789/p1700000000000100" "Repl
|
|
|
71
81
|
# Resolves via users.list, then the channel's members so Slack Connect guests work;
|
|
72
82
|
# any handle that can't be resolved is left as plain text. The confirm preview shows
|
|
73
83
|
# the converted message before sending. Use --no-mentions to keep @text literal.
|
|
74
|
-
slack send "#general" "thanks @t.
|
|
84
|
+
slack send "#general" "thanks @t.yamada19850101 and @alice"
|
|
75
85
|
slack send "#general" "ping @ops on call" --no-mentions # leave @ops as plain text
|
|
76
86
|
|
|
77
87
|
# Edit or delete a sent message (same confirm-code gate)
|
|
@@ -83,6 +93,14 @@ slack delete "<permalink>"
|
|
|
83
93
|
slack react "#general:1700000000.000100" white_check_mark
|
|
84
94
|
slack react "<permalink>" eyes --remove # take a reaction back
|
|
85
95
|
|
|
96
|
+
# Task tracking on top of reactions — :pushpin: marks a message as a task,
|
|
97
|
+
# a second reaction carries its progress (see "todo" below)
|
|
98
|
+
slack todo ls # my open tasks
|
|
99
|
+
slack todo ls --state untriaged --in "#general"
|
|
100
|
+
slack todo set "#general:1700000000.000100" doing
|
|
101
|
+
slack todo flag "<permalink>" blocked
|
|
102
|
+
slack todo doctor --in "#general" # find messages stuck in two states
|
|
103
|
+
|
|
86
104
|
# Bulk export channel history
|
|
87
105
|
slack dump --days 7 --filter eng
|
|
88
106
|
|
|
@@ -94,6 +112,89 @@ slack tail "#general" --me # only messages that mention you
|
|
|
94
112
|
slack tail "@bob" --exit-on-message --timeout 30m # wait for a reply, then exit
|
|
95
113
|
```
|
|
96
114
|
|
|
115
|
+
### todo — task tracking on reactions
|
|
116
|
+
|
|
117
|
+
Tasks are just messages. A **marker** reaction (📌 `:pushpin:`) says "this is a task";
|
|
118
|
+
a **progress** reaction says where it stands; **reason flags** stack on top:
|
|
119
|
+
|
|
120
|
+
| axis | reaction | meaning |
|
|
121
|
+
| --- | --- | --- |
|
|
122
|
+
| progress 1 | ✅ `white_check_mark` | done |
|
|
123
|
+
| progress 2 | 🚫 `no_entry_sign` | dropped |
|
|
124
|
+
| progress 3 | 👀 `eyes` | doing |
|
|
125
|
+
| progress 4 | ⏳ `hourglass_flowing_sand` | pending |
|
|
126
|
+
| progress 5 | *(marker only)* | undefined / untriaged |
|
|
127
|
+
| flag | ❗ `exclamation` | alert |
|
|
128
|
+
| flag | ❓ `question` | needs discussion |
|
|
129
|
+
| flag | 💬 `speech_balloon` | waiting — the other party in this conversation owes a reply |
|
|
130
|
+
| flag | 🔒 `lock` | blocked — stuck on something *outside* this conversation |
|
|
131
|
+
|
|
132
|
+
The invariant is **"at least one progress reaction, readers collapse by priority"** —
|
|
133
|
+
not "exactly one". A message carrying both ✅ and 👀 reads as *done*.
|
|
134
|
+
|
|
135
|
+
**Whose ball is it?** There are only three answers, and `pending` ⏳ already means *mine*.
|
|
136
|
+
The other two are the flags:
|
|
137
|
+
|
|
138
|
+
- 💬 **waiting** — I've done my part; someone **in this conversation** owes the next move
|
|
139
|
+
(I asked a question, I'm waiting on a review, I sent it and need a yes/no).
|
|
140
|
+
- 🔒 **blocked** — the hold-up is **outside this conversation**: another task, a third
|
|
141
|
+
party, an external dependency, a deploy window.
|
|
142
|
+
|
|
143
|
+
They stack with each other and with ❗/❓; `--state stuck` matches a task with any of them.
|
|
144
|
+
|
|
145
|
+
```sh
|
|
146
|
+
slack todo ls # --state open (default): marked, not done/dropped
|
|
147
|
+
slack todo ls --state untriaged # marked, no progress reaction yet
|
|
148
|
+
slack todo ls --state doing --in "#eng" # scoped to a channel
|
|
149
|
+
slack todo ls --state stuck # unfinished AND carrying a reason flag
|
|
150
|
+
slack todo ls --state stuck --from "@alice" # …and it was alice who wrote it
|
|
151
|
+
slack todo ls --from me # tasks on my own messages
|
|
152
|
+
slack todo ls --state done --shared # anyone's reactions, not just mine
|
|
153
|
+
slack todo set "#eng:1700000000.000100" doing
|
|
154
|
+
slack todo flag "<permalink>" waiting # their ball now
|
|
155
|
+
slack todo flag "<permalink>" needs-discussion
|
|
156
|
+
slack todo flag "<permalink>" blocked --remove
|
|
157
|
+
slack todo doctor --in "#eng" # report messages in two progress states
|
|
158
|
+
slack todo doctor --in "#eng" --fix # keep the highest-priority one
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Notes:
|
|
162
|
+
|
|
163
|
+
- `todo ls` is read-only and runs **exactly one** `search.messages` call — priority is
|
|
164
|
+
expressed as negated `hasmy:` terms in the query, not as multiple searches
|
|
165
|
+
(`search.messages` is Tier 2, ~20 req/min). By default it matches only *your* reactions
|
|
166
|
+
(`hasmy:`); `--shared` switches to anyone's (`has:`). Slack's search index lags a little
|
|
167
|
+
behind `reactions.add`, so a just-set task may take a moment to appear.
|
|
168
|
+
- `todo set` **adds the new reaction before removing the old ones**, and removes serially.
|
|
169
|
+
The reverse order would leave a window with no progress reaction at all — a crash or a
|
|
170
|
+
429 there would drop the task out of every query with nothing left pointing at it.
|
|
171
|
+
Worst case here is a message with two progress reactions, which reads correctly and is
|
|
172
|
+
repairable with `todo doctor --fix`.
|
|
173
|
+
- `--from <@user|me>` maps to search's `from:` modifier (a missing `@` is supplied; `me`
|
|
174
|
+
passes through as-is). Reactions carry no reference to *whom* a task waits on, but the
|
|
175
|
+
sender is usually that person — so filtering by author answers most of the question for
|
|
176
|
+
free. It composes with `--in` in the same single search.
|
|
177
|
+
- **Not implemented, deliberately:** encoding the actual reference ("blocked on @alice" /
|
|
178
|
+
"blocked on task X") as a machine-readable token in a thread reply. Measured: Slack's
|
|
179
|
+
full-text index splits on punctuation, so a quoted search for `"blocked-on"` returns 0
|
|
180
|
+
hits, and `"1.91"` matches `1.91.0`. A token like `todo:v1 blocked-on=@alice` is therefore
|
|
181
|
+
unsearchable. Any future attempt needs a single alphanumeric marker word (e.g. `tdblock`)
|
|
182
|
+
plus a bare `@mention`.
|
|
183
|
+
- `todo doctor` pages history sequentially and scans at most `--limit` messages
|
|
184
|
+
(default 1000); if it hits the limit it says so rather than silently stopping.
|
|
185
|
+
- Emoji are configurable in `~/.config/slack-cli/todo.json` (same directory as
|
|
186
|
+
`profiles.json`); anything omitted falls back to the defaults above:
|
|
187
|
+
|
|
188
|
+
```json
|
|
189
|
+
{ "marker": "round_pushpin", "progress": { "doing": "construction" } }
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
- Channel-name→ID and user-ID→handle lookups are cached in
|
|
193
|
+
`~/.config/slack-cli/cache.json` for 1 hour, namespaced by workspace (`team_id`) since
|
|
194
|
+
both are workspace-scoped; `--no-cache` bypasses it. Reaction state and search results
|
|
195
|
+
are **never** cached — they are exactly the values that change, and Slack's search index
|
|
196
|
+
already lags. A corrupt or unwritable cache is ignored and the command runs uncached.
|
|
197
|
+
|
|
97
198
|
### tail — real-time message stream
|
|
98
199
|
|
|
99
200
|
`slack tail` polls a channel every 3 seconds (configurable via `--interval`) and
|
|
@@ -101,10 +202,10 @@ prints new messages as they arrive, in the same `[ts] @handle: text` format as
|
|
|
101
202
|
the `read` command.
|
|
102
203
|
|
|
103
204
|
```sh
|
|
104
|
-
slack tail "#
|
|
105
|
-
slack tail "#
|
|
106
|
-
slack tail "#
|
|
107
|
-
slack tail "#
|
|
205
|
+
slack tail "#general" # follow new messages from now
|
|
206
|
+
slack tail "#general" --since=30m # backfill 30 minutes, then stream
|
|
207
|
+
slack tail "#general" --thread=1700000000.000100 # one thread only
|
|
208
|
+
slack tail "#general" --me # only messages mentioning you
|
|
108
209
|
```
|
|
109
210
|
|
|
110
211
|
For automation, `--exit-on-message` stops as soon as the first message from
|
|
@@ -113,7 +214,7 @@ For automation, `--exit-on-message` stops as soon as the first message from
|
|
|
113
214
|
make a "wait for a reply, then act" primitive that won't hang:
|
|
114
215
|
|
|
115
216
|
```sh
|
|
116
|
-
slack tail "@
|
|
217
|
+
slack tail "@alice" --exit-on-message --timeout 30m --interval 15000
|
|
117
218
|
```
|
|
118
219
|
|
|
119
220
|
**Note:** Cross-channel mention streaming (`--me` without a target) is not yet
|
package/SKILL.md
CHANGED
|
@@ -55,6 +55,10 @@ slack search "deploy" --count 50
|
|
|
55
55
|
# Read a specific channel or DM (quote #channel — unquoted # is a shell comment)
|
|
56
56
|
slack read "#general"
|
|
57
57
|
slack read @username
|
|
58
|
+
# Thread parents are marked `[+N replies]` — a marked line means there is more to
|
|
59
|
+
# read inside the thread (`slack thread "#general" <ts>`); top-level output alone
|
|
60
|
+
# never shows thread replies. --json carries reply_count/reply_users_count/latest_reply.
|
|
61
|
+
slack read "#general" --unreplied # only what's still awaiting your answer
|
|
58
62
|
|
|
59
63
|
# Send a message (two-step confirm)
|
|
60
64
|
slack send "#general" "Hello team"
|
|
@@ -74,6 +78,16 @@ slack delete "<permalink>"
|
|
|
74
78
|
slack react "#general:1700000000.000100" white_check_mark
|
|
75
79
|
slack react "<permalink>" eyes --remove # take it back
|
|
76
80
|
|
|
81
|
+
# Task tracking on reactions — 📌 marks a task, a second reaction holds progress
|
|
82
|
+
slack todo ls # my open tasks (read-only, 1 search)
|
|
83
|
+
slack todo ls --state untriaged --in "#general" # marked but no progress yet
|
|
84
|
+
slack todo ls --state stuck # unfinished + a reason flag
|
|
85
|
+
slack todo set "#general:1700000000.000100" doing # pending|doing|done|dropped
|
|
86
|
+
slack todo ls --state stuck --from "@alice" # …and alice wrote it
|
|
87
|
+
slack todo flag "<permalink>" waiting # alert|needs-discussion|waiting|blocked
|
|
88
|
+
# waiting = their ball, blocked = outside
|
|
89
|
+
slack todo doctor --in "#general" [--fix] # messages stuck in two states
|
|
90
|
+
|
|
77
91
|
# Bulk export a channel's history
|
|
78
92
|
slack dump "#channel-name"
|
|
79
93
|
|
|
@@ -85,11 +99,24 @@ slack download "https://acme.slack.com/files/U1/F0123ABC/invoice.pdf"
|
|
|
85
99
|
|
|
86
100
|
Targets for `send` are `#channel`, `@user`, `#channel:<thread_ts>`, or a Slack URL —
|
|
87
101
|
a message permalink replies in that message's thread; a channel-only URL posts top-level.
|
|
88
|
-
The confirm preview prints
|
|
89
|
-
|
|
102
|
+
The confirm preview prints who it will be sent **as** (`From: @handle (Uxxxx) — Workspace`,
|
|
103
|
+
plus `[as bot]` under `--as-bot`) and the resolved destination (`→ ... — THREAD REPLY` or
|
|
104
|
+
`→ ... — NEW top-level message`); verify both before re-running with `--code`.
|
|
105
|
+
|
|
106
|
+
**Every** gated write opens its preview with that same `From:` line — `send`, `edit`,
|
|
107
|
+
`delete`, `upload`, `schedule send`, `schedule rm`, `channel create`, `drafts edit`,
|
|
108
|
+
`drafts delete` — and the identity is part of the confirm hash: switching
|
|
109
|
+
profile/token between preview and confirm invalidates the code instead of acting as
|
|
110
|
+
someone else. It matters most on `schedule send`, where the message goes out later,
|
|
111
|
+
unattended, as whoever the token was at schedule time.
|
|
112
|
+
|
|
113
|
+
`schedule send` prints its fire time in both zones —
|
|
114
|
+
`At: 2026-08-10 18:00:00 GMT+9 (local) / 2026-08-10T09:00:00.000Z (Unix: 1786352400)` —
|
|
115
|
+
because `--at` accepts both bare local times (`"2026-08-10 09:00"`) and `Z`-suffixed
|
|
116
|
+
UTC, and the two are easy to mix up. Check the side you meant before confirming.
|
|
90
117
|
|
|
91
118
|
`send` also runs a **warn-only untagged-mention lint**: if the body names a workspace
|
|
92
|
-
member in plain text (honorifics like
|
|
119
|
+
member in plain text (honorifics like `山田さん` / `王老师`, or `Hi Dave`) without a
|
|
93
120
|
matching `<@USERID>` tag, it prints `⚠ possible untagged mention: …` alongside the
|
|
94
121
|
confirm code. It never blocks — third-party/external references are expected to trip it.
|
|
95
122
|
To actually notify someone, write `@handle` (auto-converted to `<@USERID>`; see below).
|
|
@@ -103,8 +130,52 @@ and no one has replied since — `⚠ 相手はまだ返信していません…
|
|
|
103
130
|
on that message instead of piling on a new one. Reply-status based, not time-based; opt
|
|
104
131
|
out with `SLACK_UNREPLIED_WARN=0`.
|
|
105
132
|
|
|
133
|
+
### todo — tasks as reactions
|
|
134
|
+
|
|
135
|
+
A task is any message carrying the marker reaction 📌 `:pushpin:`. Progress lives in a
|
|
136
|
+
second reaction, ranked by priority — **1** ✅ `white_check_mark` done, **2** 🚫
|
|
137
|
+
`no_entry_sign` dropped, **3** 👀 `eyes` doing, **4** ⏳ `hourglass_flowing_sand` pending, **5** marker
|
|
138
|
+
only = undefined. A message may carry more than one progress reaction — readers collapse
|
|
139
|
+
to the highest priority, so ✅+👀 reads as *done*.
|
|
140
|
+
|
|
141
|
+
Reason flags stack independently of progress and of each other: ❗ `exclamation` alert,
|
|
142
|
+
❓ `question` needs-discussion, and two that say **whose ball it is** —
|
|
143
|
+
|
|
144
|
+
- 💬 `speech_balloon` **waiting** — someone **in this conversation** owes the next move
|
|
145
|
+
(asked a question, waiting on a review/approval). Their ball.
|
|
146
|
+
- 🔒 `lock` **blocked** — stuck on something **outside** it: another task, a third party,
|
|
147
|
+
an external dependency. Nobody here can unstick it by replying.
|
|
148
|
+
|
|
149
|
+
(⏳ `hourglass_flowing_sand` pending already means "my ball", which is why only these two are needed.)
|
|
150
|
+
|
|
151
|
+
- `todo ls` is read-only and issues **one** `search.messages` call; priority is encoded as
|
|
152
|
+
negated `hasmy:` terms. Default is `hasmy:` (my reactions only), `--shared` uses `has:`.
|
|
153
|
+
`search.messages` returns no reaction data, so this filtering can only happen in the
|
|
154
|
+
query. The search index lags slightly behind `reactions.add`.
|
|
155
|
+
- `todo set` adds the target reaction **first**, then removes the stale ones one at a time.
|
|
156
|
+
Never remove-then-add: an interruption in between leaves the task with no progress
|
|
157
|
+
reaction and it vanishes from every query. Two reactions is the safe failure mode, and
|
|
158
|
+
`todo doctor --fix` repairs it (also add-first).
|
|
159
|
+
- `todo ls --from <@user|me>` adds search's `from:` modifier (missing `@` supplied, `me`
|
|
160
|
+
left bare) and composes with `--in` in the same single search. Reactions can't point at
|
|
161
|
+
*who* a task waits on, but the sender usually is that person.
|
|
162
|
+
- Deliberately NOT implemented: a machine-readable token in a thread reply to carry the
|
|
163
|
+
actual reference. Measured — Slack's index splits on punctuation, so quoted `"blocked-on"`
|
|
164
|
+
gets 0 hits and `"1.91"` matches `1.91.0`; `todo:v1 blocked-on=@alice` is unsearchable.
|
|
165
|
+
A future attempt needs one alphanumeric marker word (e.g. `tdblock`) + a bare `@mention`.
|
|
166
|
+
- `todo doctor` scans at most `--limit` messages (default 1000), pages sequentially, and
|
|
167
|
+
explicitly reports when it hits the limit.
|
|
168
|
+
- Emoji overrides: `~/.config/slack-cli/todo.json`, e.g.
|
|
169
|
+
`{ "marker": "round_pushpin", "progress": { "doing": "construction" } }`.
|
|
170
|
+
- Channel/user lookups are cached for 1h in `~/.config/slack-cli/cache.json`, keyed by
|
|
171
|
+
workspace `team_id` (`--no-cache` to bypass). Reaction state and search results are never
|
|
172
|
+
cached; a broken cache is ignored. Message listings read this cache too, behind their
|
|
173
|
+
in-process map.
|
|
174
|
+
- Rate limits: `todo` commands catch Slack's 429, wait `Retry-After`, and retry up to 3
|
|
175
|
+
times before failing loudly.
|
|
176
|
+
|
|
106
177
|
**Etiquette:** prefer a `react` over a reply for simple acks (了解 → `eyes`, 完了 →
|
|
107
|
-
`white_check_mark`, 処理中 → `
|
|
178
|
+
`white_check_mark`, 処理中 → `hourglass_flowing_sand`) so threads stay short; read the thread with
|
|
108
179
|
`slack thread`/`slack read` before replying to avoid duplicates; consolidate multiple
|
|
109
180
|
points into one message.
|
|
110
181
|
|
|
@@ -177,7 +248,7 @@ slack news --limit 1
|
|
|
177
248
|
- **`xoxc-` desktop session token** — accepted by the public Slack API (send/edit/delete/react/upload/channel create+invite included) as long as its `xoxd` session cookie is attached (`slack auth chrome`/`slack auth firefox`, or `SLACK_COOKIE=...`). Without the cookie it still fails with a clear "needs its session cookie" error.
|
|
178
249
|
- **`conversations.invite` reports success but the member never shows up** — the invited user is likely a single-channel guest (`is_ultra_restricted`); Slack silently no-ops the invite since that account type can only ever belong to the one channel it was created in. `channel create --invite` now warns about this up front.
|
|
179
250
|
- **Send is rejected with "use #channel or @user"** — the CLI enforces human-readable targets. Use `#channel-name` or `@display-name`, not raw IDs.
|
|
180
|
-
- **Confirm code mismatch on
|
|
251
|
+
- **Confirm code mismatch on any gated write** — the content, target or acting identity changed between preview and confirm (a different profile/`SLACK_TOKEN`, or `--as-bot` added or dropped). Re-run without `--code` for a fresh preview, and re-check the `From:` line and the `→` destination line (THREAD REPLY vs NEW top-level message).
|
|
181
252
|
- **Enterprise Grid / admin-locked workspace** — custom app installation may need admin approval or be disabled outright.
|
|
182
253
|
|
|
183
254
|
## Safety
|