slack-term 1.24.0 → 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 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 a destination preview + confirm code; rerun with --code=<code> to actually send
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.matsuda19790127 and @taku"
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 "#symval" # follow new messages from now
105
- slack tail "#symval" --since=30m # backfill 30 minutes, then stream
106
- slack tail "#symval" --thread=1700000000.000100 # one thread only
107
- slack tail "#symval" --me # only messages mentioning you
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 "@matsuda" --exit-on-message --timeout 30m --interval 15000
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 the resolved destination (`→ ... — THREAD REPLY` or
89
- `→ ... — NEW top-level message`); verify it before re-running with `--code`.
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 `松田さん` / `張老师`, or `Hi Dave`) without a
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`, 処理中 → `hourglass`) so threads stay short; read the thread with
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 `send`** — the message text or destination changed between preview and confirm. Re-run without `--code` for a fresh preview, and re-check the `→` destination line (THREAD REPLY vs NEW top-level message).
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