slack-term 1.35.0 → 1.37.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
@@ -126,7 +126,9 @@ slack react "<permalink>" eyes --remove # take a reaction back
126
126
  slack ask "@bob" "本番に出してよい?" "出してよい" "待って"
127
127
  # --wait blocks until answered and prints ONLY the answer on stdout, so it composes:
128
128
  ANS=$(slack ask "@bob" "本番に出してよい?" "出してよい" "待って" --code=<code> --wait)
129
- # exit 0 = answered, 2 = timed out (--timeout, default 3600s), 3 = transport failure.
129
+ # exit 0 = answered, 2 = timed out (--timeout, default 3600s), 3 = transport failure,
130
+ # 4 = a reply matched several choices, 5 = a free-text reply that picked NO choice —
131
+ # the reply text is on stdout, it is not a decision, and the question stays open.
130
132
  #
131
133
  # The question must say WHO may answer — only their reaction/reply is taken as the
132
134
  # answer, so a bystander can't decide it for them. `ask` refuses to post otherwise.
@@ -144,14 +146,26 @@ RESUME=$(slack ask "@bob" "本番に出してよい?" "出してよい" "待っ
144
146
  # -> slack ask --waitFor='https://acme.slack.com/archives/C00000001/p1700000000000100'
145
147
  # Run it whenever you like: it re-reads the question from Slack, so nothing is
146
148
  # stored locally and any machine holding the link can collect. Same stdout/exit
147
- # contract as --wait, plus --timeout 0 = check once (exit 2 while still open, 4 if
148
- # somebody replied without choosing any of the offered options),
149
+ # contract as --wait, plus --timeout 0 = check once (exit 2 while still open, 5 with
150
+ # the reply on stdout if somebody wrote free text instead of choosing; re-wait past
151
+ # it with --after=<reply ts>),
149
152
  # which is what a periodic monitor should use instead of parking on --wait.
150
153
  eval "$RESUME --timeout 0" && echo answered
151
154
  # A pressed pill is invisible to `slack tail` (it only sees `type: "message"`
152
155
  # events and drops `message_changed`), so this is the only way to hear about an
153
156
  # answer to a question you did not block on.
154
157
 
158
+ # Who sent what: every send/ask/poll/edit is logged locally with its sender —
159
+ # pid, cwd, git branch, agent CLI and session id — and the same attribution rides
160
+ # along as invisible Slack message metadata (event_type `slack_term_sent`).
161
+ slack sent # newest first, sender on its own line
162
+ slack sent "deploy" --since 2h --kind ask # substring of the text
163
+ slack sent --session 4f1c --cwd ~/ws/app --json
164
+ # Log: ~/.config/slack-cli/sent.sqlite (SLACK_TERM_SENT_DB overrides).
165
+ # Opt out of both with SLACK_TERM_ATTRIBUTION=off. Agent CLIs other than Claude
166
+ # Code are found by process name; set SLACK_TERM_AGENT_SESSION / _CLI / _PID to
167
+ # name the session explicitly.
168
+
155
169
  # Task tracking on top of reactions — :pushpin: marks a message as a task,
156
170
  # a second reaction carries its progress (see "todo" below)
157
171
  slack todo ls # my open tasks
@@ -321,6 +335,24 @@ Or place it in `~/.config/slack-cli/.env` or a local `.env` file.
321
335
 
322
336
  See [`SKILL.md`](SKILL.md) for a full token-acquisition walkthrough.
323
337
 
338
+ ### Ubuntu desktop session
339
+
340
+ Sign in to Slack in Chrome or Slack Desktop. `--from-chrome` reads a browser `xoxc-` token and cookie from the same Chrome profile; desktop import reads `xoxc-` tokens from native, Snap, or Flatpak data directories. Browser profiles contain sensitive cookies, so the CLI asks `Read local browser profiles and Slack session cookies? [y/N]` before scanning. Enter `y` to allow a scan; the default is no.
341
+
342
+ ```sh
343
+ slack auth login --from-desktop # desktop token only
344
+ slack auth login --from-chrome # Chrome token + Chrome cookie
345
+ slack auth login --from-firefox # desktop token + Firefox cookie
346
+ slack auth login --from-all # desktop and browser sources
347
+ slack auth login --from-chrome --yes # bypass the browser-read prompt
348
+ slack auth tokens # print active credentials in .env format
349
+ slack auth save --envfile=./.env.local # export active token + cookie
350
+ ```
351
+
352
+ `--from-all` attaches a cookie only when exactly one browser session is found. If several are found, the desktop token is saved without a cookie; use `slack auth chrome -w <name>` or `slack auth firefox -w <name>` to select the matching profile. These commands also ask before reading browser profiles, and accept `--yes` for scripts. Select the saved workspace with `slack auth use -g <name>`. `auth tokens` prints the active `SLACK_TOKEN`, optional `SLACK_COOKIE`, and optional `SLACK_BOT_TOKEN` to stdout; treat its output as secret. `auth save` requires a cookie, writes `SLACK_TOKEN` and `SLACK_COOKIE`, and makes the env file owner-readable only on Unix. `--workspace <name>` selects a specific profile.
353
+
354
+ Chrome supports Linux `v10` cookies and `v11` cookies when `secret-tool` can read the unlocked GNOME keyring. Other Linux keyring backends are not yet supported. Firefox discovery covers native, Snap, and Flatpak profiles. If browser session access is unavailable, use `slack auth token` to add a user token from a Slack app. Treat desktop tokens and browser cookies as credentials; keep profile files and local env files private.
355
+
324
356
  ## Development
325
357
 
326
358
  ```sh
package/SKILL.md CHANGED
@@ -162,15 +162,17 @@ without losing clicks. A reaction is durable state anyone can read back later.
162
162
  - **Two pills pressed = no answer.** Changing your mind leaves both reactions in place, so
163
163
  `ask` says so in the thread once and keeps waiting rather than guessing.
164
164
  - **Exit codes** are the contract: `0` answered (the answer alone on stdout), `2` nobody
165
- replied, `3` transport/config failure, `4` somebody replied but picked none of the
166
- offered choices. Everything human-facing goes to stderr, so
165
+ replied, `3` transport/config failure, `4` a reply matched several choices (stdout
166
+ empty), `5` a **free-text reply** that picked none of the offered choices — the reply
167
+ text is on stdout. Everything human-facing goes to stderr, so
167
168
  `ANS=$(slack ask … --wait)` is safe.
168
- - **A reply that chooses nothing is not an answer.** When choices were offered, only a
169
- reaction, the choice text, or its number counts. A reply like 「意味が分かりません」 or a
170
- question back exits `4` with **stdout empty** — measured 2026-09-04, a clarifying
171
- question was returned as `rc=0` and stored as the decision, which is what an automated
172
- caller acts on. `2` and `4` need different reactions: one waits, the other answers a
173
- person who is standing there.
169
+ - **A reply that chooses nothing is delivered, but is not a decision.** When choices were
170
+ offered, only a reaction, the choice text, or its number is a decision (`0`). A reply
171
+ like 「背景を教えて」 or 「3 でなく X にして」 exits `5` at once with the text on stdout
172
+ — read it and act on it; do not treat it as a chosen option (measured 2026-09-04, a
173
+ question back returned as `rc=0` was stored as the decision). The question stays open,
174
+ so a pill pressed later still decides it: re-wait past the delivered reply with
175
+ `slack ask --waitFor='<permalink>' --after=<reply ts>` (stderr prints this line).
174
176
 
175
177
  **Collecting an answer you did not block on.** Without `--wait`, stdout is a runnable
176
178
  `slack ask --waitFor='<permalink>'`. Run it any time — it re-reads the question from Slack
@@ -184,6 +186,22 @@ This path is not optional convenience — **a pressed pill is invisible to `slac
184
186
  which only sees `type: "message"` events and drops `message_changed`. Without `--waitFor`
185
187
  an answer can be pressed and nobody ever hears about it.
186
188
 
189
+ ### sent — what did I post, and which session posted it
190
+
191
+ Every `send`/`ask`/`poll`/`edit` is logged locally (`~/.config/slack-cli/sent.sqlite`)
192
+ with its sender: agent pid, cwd, git branch, agent CLI and session id. The same
193
+ attribution is attached as Slack message `metadata` (event_type `slack_term_sent`) —
194
+ invisible in the client, readable through the API from any machine.
195
+
196
+ ```bash
197
+ slack sent "deploy" --since 2h # text substring, newest first
198
+ slack sent --kind ask --session <id-prefix> # this session's questions
199
+ slack sent --cwd . --json # everything posted from this repo, every field
200
+ ```
201
+
202
+ Read-only and token-free. `SLACK_TERM_ATTRIBUTION=off` disables both the log and the
203
+ metadata; `SLACK_TERM_AGENT_SESSION` / `_CLI` / `_PID` override what is detected.
204
+
187
205
  ### todo — tasks as reactions
188
206
 
189
207
  A task is any message carrying the marker reaction 📌 `:pushpin:`. Progress lives in a
@@ -304,7 +322,7 @@ slack news --limit 1
304
322
  - **`missing_scope`** — add the scope from the error, then click **Reinstall to Workspace** (scope changes require reinstall).
305
323
  - **`token_revoked`** — app uninstalled; reinstall from the app page.
306
324
  - **Token starts with `xoxb-`** — that's a Bot Token. Add scopes under **User Token Scopes** instead, reinstall, and copy the **User OAuth Token**.
307
- - **`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.
325
+ - **`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 firefox` on Ubuntu, `slack auth chrome` on macOS/Linux, or `SLACK_COOKIE=...`). Without the cookie it still fails with a clear "needs its session cookie" error.
308
326
  - **`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.
309
327
  - **Send is rejected with "use #channel or @user"** — the CLI enforces human-readable targets. Use `#channel-name` or `@display-name`, not raw IDs.
310
328
  - **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).