slack-term 1.41.2 → 1.43.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 +82 -0
- package/SKILL.md +18 -0
- package/dist/cli.js +228 -208
- package/package.json +2 -1
- package/ts/cli.ts +472 -83
- package/ts/newlines.ts +158 -0
- package/ts/relay.ts +105 -0
- package/ts/sentlog.ts +106 -2
- package/ts/slack.ts +52 -4
- package/ts/stream.ts +1048 -0
package/README.md
CHANGED
|
@@ -173,6 +173,12 @@ eval "$RESUME --timeout 0" && echo answered
|
|
|
173
173
|
# A pressed pill is invisible to `slack tail` (it only sees `type: "message"`
|
|
174
174
|
# events and drops `message_changed`), so this is the only way to hear about an
|
|
175
175
|
# answer to a question you did not block on.
|
|
176
|
+
#
|
|
177
|
+
# Answered but never collected? Asks from THIS machine (local sent-log) whose pill was
|
|
178
|
+
# pressed, or that got a reply, while nobody was waiting — grouped by the asking
|
|
179
|
+
# session, with whether that agent is still alive and the --waitFor line that
|
|
180
|
+
# collects each. Read-only: collecting (that line) is what takes it off the list.
|
|
181
|
+
slack ask --pending # last 7 days; --since 2h, --channel, --json
|
|
176
182
|
|
|
177
183
|
# Who sent what: every send/ask/poll/edit is logged locally with its sender —
|
|
178
184
|
# pid, cwd, git branch, agent CLI and session id — and the same attribution rides
|
|
@@ -374,6 +380,82 @@ slack tail "@alice" --exit-on-message --timeout 30m --interval 15000
|
|
|
374
380
|
**Note:** Cross-channel mention streaming (`--me` without a target) is not yet
|
|
375
381
|
supported — a target channel is required.
|
|
376
382
|
|
|
383
|
+
### stream — every matching message, across all channels
|
|
384
|
+
|
|
385
|
+
`slack stream --grep <regex>` watches **every** conversation the identity is in —
|
|
386
|
+
new top-level posts *and* thread replies — and prints each message whose text
|
|
387
|
+
matches. It is the cross-channel counterpart of `tail`, built for a long-running
|
|
388
|
+
consumer (e.g. "wake an agent whenever someone @mentions the bot"):
|
|
389
|
+
|
|
390
|
+
```sh
|
|
391
|
+
slack stream --grep '<@U00000001>|@mybot' --json # runs until stopped
|
|
392
|
+
slack stream --grep 'deploy' -i --channel '#dev' --once # one scan, then exit
|
|
393
|
+
slack stream --grep '<@U00000001>' --since 2h --json # replay the last 2 hours
|
|
394
|
+
slack stream --grep '<@U00000001>' --replies-to self --json # + every reply in threads the bot is in
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
- **One JSON line per match** with `--json`:
|
|
398
|
+
`{type: "message"|"reply", channel: {id, name}, ts, thread_ts, user: {id, name}, text, permalink, match}`,
|
|
399
|
+
where `match` is `"grep"` or `"replies-to"`.
|
|
400
|
+
`--grep` runs on the raw text (mentions look like `<@U…>`), plus legacy attachment
|
|
401
|
+
text; the emitted `text` has `< > &` decoded.
|
|
402
|
+
- **Privacy-safe defaults.** With a bot token (`SLACK_BOT_TOKEN`) it streams **as the
|
|
403
|
+
bot**, so it sees only channels the bot was invited to — never the user's DMs.
|
|
404
|
+
`--as-user` opts into the user identity. Matching happens before anything is
|
|
405
|
+
printed: a non-matching message is never printed, logged, or even name-resolved.
|
|
406
|
+
- **Replies in a user's threads.** `--replies-to <user-id>` (repeatable; `self` = the
|
|
407
|
+
streaming identity) also prints every reply in a thread that user started or has
|
|
408
|
+
replied in, whether or not it matches `--grep` — people answer a bot in its thread
|
|
409
|
+
without tagging it. It matches Slack's `parent_user_id`, the parent's
|
|
410
|
+
`reply_users`, and the id's own replies the stream has seen (kept in the state
|
|
411
|
+
file for 30 days). `reply_users` lists at most five people, so a thread cut short
|
|
412
|
+
there is read once in full (as is one whose list is missing). A bot can appear
|
|
413
|
+
as its user id (`U…`) or bot id (`B…`); `self`, or a `B…` id, covers both. Polling only reaches threads inside `--thread-window`;
|
|
414
|
+
with the relay, a reply in an older thread is caught too.
|
|
415
|
+
- **No self-echo.** Posts by the streaming identity are excluded by sender id (its user
|
|
416
|
+
id and bot id; with `--as-user`, the bot's posts too) — the same text from anyone
|
|
417
|
+
else still matches.
|
|
418
|
+
- **Resumable.** A per-channel cursor (plus one per active thread) is saved under
|
|
419
|
+
`$XDG_STATE_HOME/slack-term/stream/` (one file per identity + grep + channels + `--replies-to`;
|
|
420
|
+
override with `--state`). A restart continues where it stopped: nothing lost,
|
|
421
|
+
nothing repeated. The first run starts *now*; `--since` replays from a point,
|
|
422
|
+
ignoring the saved cursor. Delivery is at-least-once only in one corner — a crash
|
|
423
|
+
between printing a match and saving the cursor repeats that line, so dedupe on
|
|
424
|
+
`channel.id + ts`. A lock file keeps two streams off the same state.
|
|
425
|
+
- **Transport: the relay is the primary mode; polling is the fallback.** Deploy
|
|
426
|
+
[`worker/`](worker/) (a Cloudflare Worker on Slack's Events API — setup in
|
|
427
|
+
[worker/README.md](worker/README.md)) and set `SLACK_RELAY_URL` and
|
|
428
|
+
`SLACK_RELAY_TOKEN`: the stream then hears a *doorbell* — `{channel, ts, thread_ts}`,
|
|
429
|
+
no text — within a second of each post, reads that one message through the Web API
|
|
430
|
+
with its own token and runs it through the same `--grep`. While the relay is
|
|
431
|
+
connected the poll still runs, every `--reconcile` (default `5m`), as the safety net;
|
|
432
|
+
when the relay is down it goes back to `--interval`. Both paths share one record of
|
|
433
|
+
what was printed, so a message is never printed twice. The relay is used only when
|
|
434
|
+
streaming as the bot (it rings for the bot's channels) and never with `--once`.
|
|
435
|
+
- **Polling fallback** (no relay configured, `--as-user`, or `--no-relay`). The stream
|
|
436
|
+
prints a `WARNING: polling mode` line on stderr, pointing to the relay setup. It
|
|
437
|
+
polls every `--interval` (default `45s`), paced to stay inside Slack's rate limits:
|
|
438
|
+
- every cycle reads each channel's history **from its cursor only** (new posts);
|
|
439
|
+
- each channel's whole `--thread-window` (default `3d`) — which shows which threads
|
|
440
|
+
gained replies, and only those threads are read — is re-read once per
|
|
441
|
+
`--thread-scan` (default `3m`), spread across cycles rather than all at once, so a
|
|
442
|
+
reply under an older parent can take up to `--thread-scan` to appear;
|
|
443
|
+
- a channel with no post inside the window skips that re-read, and such an idle
|
|
444
|
+
DM/MPIM (`--as-user`) is not read at all between `--thread-scan` periods;
|
|
445
|
+
- all `conversations.history` / `conversations.replies` calls share one token
|
|
446
|
+
bucket, `--calls-per-min` (default `40`, under Tier 3's ~50/min; 10 at once).
|
|
447
|
+
A message is held back until it is 5 s old, so a cursor never skips one Slack has
|
|
448
|
+
not made visible yet. Rate limits honour `Retry-After`.
|
|
449
|
+
- **Not covered:** edits (a message edited *into* matching is not re-emitted), and —
|
|
450
|
+
without the relay — replies in a thread whose parent is older than `--thread-window`.
|
|
451
|
+
- **Failures are loud and distinguishable.** A network/API error retries the cycle
|
|
452
|
+
with φ backoff (`2s·1.618ⁿ`, capped at 5 min), one stderr line per attempt; after
|
|
453
|
+
12 consecutive failures (≈16 min) it exits. A channel it cannot read is skipped with
|
|
454
|
+
one stderr line; if *every* channel fails, that is a failure, not "no matches".
|
|
455
|
+
- **Exit codes** (same contract as `ask`): `0` matches printed (`--once`) or stopped by
|
|
456
|
+
SIGINT/SIGTERM; `2` `--once` found nothing; `3` transport/auth failure (stderr says
|
|
457
|
+
which); `1` bad arguments (e.g. an invalid regex).
|
|
458
|
+
|
|
377
459
|
## Configuration
|
|
378
460
|
|
|
379
461
|
Requires a Slack user token (`xoxp-...`) with the following scopes:
|
package/SKILL.md
CHANGED
|
@@ -52,6 +52,15 @@ slack news --limit 5
|
|
|
52
52
|
# Recent messages across joined channels
|
|
53
53
|
slack msgs
|
|
54
54
|
|
|
55
|
+
# Every new message matching a regex, across all channels + threads (long-running;
|
|
56
|
+
# resumable cursor; as the bot when SLACK_BOT_TOKEN is set). Exit 0 match / 2 none / 3 failure.
|
|
57
|
+
# Real time = the relay (primary mode): deploy worker/ (see worker/README.md) and set
|
|
58
|
+
# SLACK_RELAY_URL + SLACK_RELAY_TOKEN. Without it the stream falls back to POLLING and
|
|
59
|
+
# warns on stderr ("WARNING: polling mode") — a fallback only: paced (--calls-per-min 40),
|
|
60
|
+
# cursor-only reads each --interval (45s), full --thread-window re-read every --thread-scan (3m).
|
|
61
|
+
slack stream --grep '<@U00000001>|@mybot' --json
|
|
62
|
+
slack stream --grep 'deploy' -i --once
|
|
63
|
+
|
|
55
64
|
# Full-text search
|
|
56
65
|
slack search "deploy"
|
|
57
66
|
slack search "deploy" --count 50
|
|
@@ -240,6 +249,15 @@ This path is not optional convenience — **a pressed pill is invisible to `slac
|
|
|
240
249
|
which only sees `type: "message"` events and drops `message_changed`. Without `--waitFor`
|
|
241
250
|
an answer can be pressed and nobody ever hears about it.
|
|
242
251
|
|
|
252
|
+
**Answered, never collected.** `slack ask --pending` (`--since 7d` default, `--channel`,
|
|
253
|
+
`--json`) checks every ask in this machine's sent-log that no `--wait`/`--waitFor` has
|
|
254
|
+
collected, and lists the ones somebody answered — grouped by the asking session, with
|
|
255
|
+
the agent pid and whether it is still alive (pid running, started before the ask, same
|
|
256
|
+
CLI), and per item the exact `collect:` line to run (cursor included). It is read-only —
|
|
257
|
+
nothing is posted, edited, stamped or relayed. Collecting is that `slack ask --waitFor=…`
|
|
258
|
+
line: an answer it hands over (exit 0) takes the ask off the list; a free-text reply it
|
|
259
|
+
hands over (exit 5) is not reported again, but a pill pressed after it is.
|
|
260
|
+
|
|
243
261
|
### sent — what did I post, and which session posted it
|
|
244
262
|
|
|
245
263
|
Every `send`/`ask`/`poll`/`edit` is logged locally (`~/.config/slack-cli/sent.sqlite`)
|