slack-term 1.51.1 → 1.51.2

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/cli.js CHANGED
@@ -251,7 +251,7 @@ Use: slack tail "#channel" --me`),process.exit(1);if(!t)console.error("Usage: sl
251
251
  `))>=0){let s=e.slice(0,n);e=e.slice(n+2);let o="message",r,i=[];for(let l of s.split(`
252
252
  `)){if(!l||l.startsWith(":"))continue;let u=l.indexOf(":"),c=u<0?l:l.slice(0,u),a=u<0?"":l.slice(u+1).replace(/^ /,"");if(c==="event")o=a;else if(c==="data")i.push(a);else if(c==="id")r=a}if(i.length)t(o,i.join(`
253
253
  `),r)}return e}var tw="slack-term-relay.v1";function nw(e){return`bearer.${Buffer.from(e,"utf8").toString("base64url")}`}function qd(e){return e==="websocket"||e==="sse"?e:"auto"}var sw=32;function Hd(e){return e===void 0?"":`?after=${e.seq}${e.epoch?`&epoch=${encodeURIComponent(e.epoch)}`:""}`}function zd(e,t,n=70000,s={}){let o=e.replace(/\/+$/,""),r=s.transport??"auto",i=rw(o,t,n);return async(l,u,c)=>{let a=s.WebSocket===void 0?globalThis.WebSocket:s.WebSocket;if(r!=="sse")if(!a){if(r==="websocket")throw Error("relay: this runtime has no WebSocket")}else{if(await ow(a,o,t,n,s.pingMs??25000,s.ackDelayMs??100,l,u,c)||u.aborted)return;if(r==="websocket")throw Error("relay: WebSocket connection failed")}return i(l,u,c)}}function ow(e,t,n,s,o,r,i,l,u){return new Promise((c,a)=>{if(l.aborted)return c(!0);let d;try{d=new e(`${t.replace(/^http/i,"ws")}/ws${Hd(i)}`,[tw,nw(n)])}catch{return c(!1)}let f=!1,p=!1,g,h,m,y=0,w=0,x=(S)=>{if(p)return;p=!0,clearTimeout(g),clearTimeout(h),clearInterval(m),l.removeEventListener("abort",D),d.onopen=d.onmessage=d.onerror=d.onclose=null;try{d.close(1000)}catch{}if(S)a(S);else c(f||l.aborted)},D=()=>x();l.addEventListener("abort",D,{once:!0});let E=()=>{clearTimeout(g),g=setTimeout(()=>x(f?Error(`relay: no data for ${Math.round(s/1000)}s`):void 0),s)},C=()=>{if(clearTimeout(h),h=void 0,y<=w)return;w=y;try{d.send(JSON.stringify({type:"ack",seq:y}))}catch{}};E(),d.onopen=()=>{f=!0,E(),m=setInterval(()=>{try{d.send("ping")}catch{}},o)},d.onmessage=(S)=>{if(E(),typeof S.data!=="string"||S.data==="pong")return;let k;try{k=JSON.parse(S.data)}catch(O){return x(O)}if(k.type==="hello"){let{type:O,...P}=k;y=w=P.seq,u.hello({...P,via:"websocket"})}else if(k.type==="bell"&&typeof k.seq==="number"){if(u.bell(k.seq,k.bell),p)return;if(y=Math.max(y,k.seq),y-w>=sw)C();else h??=setTimeout(C,r)}},d.onerror=()=>{},d.onclose=(S)=>{if(!f||S.code===1000)return x();x(Error(`relay: WebSocket closed (${S.code}${S.reason?` ${S.reason}`:""})`))}})}function rw(e,t,n){return async(s,o,r)=>{let i=new AbortController,l=()=>i.abort();o.addEventListener("abort",l,{once:!0});let u,c=!1,a=()=>{clearTimeout(u),u=setTimeout(()=>{c=!0,i.abort()},n)};try{a();let d=await fetch(`${e}/stream${Hd(s)}`,{headers:{authorization:`Bearer ${t}`,accept:"text/event-stream"},signal:i.signal});if(d.status===401)throw new Kd("relay rejected the token (401)");if(!d.ok||!d.body)throw Error(`relay: HTTP ${d.status}`);let f=d.body.getReader(),p=new TextDecoder,g="";for(;;){let{done:h,value:m}=await f.read();if(h)return;a(),g=ew((g+p.decode(m,{stream:!0})).replace(/\r\n/g,`
254
- `),(y,w,x)=>{let D=JSON.parse(w);if(y==="hello")r.hello({...D,via:"sse"});else if(y==="bell"&&x!==void 0)r.bell(Number(x),D)})}}catch(d){if(o.aborted)return;if(c)throw Error(`relay: no data for ${Math.round(n/1000)}s`);throw d}finally{clearTimeout(u),o.removeEventListener("abort",l)}}}import{existsSync as aw,mkdirSync as cw,writeFileSync as lw}from"node:fs";import{basename as uw,join as ba,resolve as Gd}from"node:path";import{randomBytes as dw}from"node:crypto";var Yd={name:"slack-term",version:"1.51.1",repository:{type:"git",url:"https://github.com/snomiao/slack-term.git"},bugs:{url:"https://github.com/snomiao/slack-term/issues"},devDependencies:{"@semantic-release/commit-analyzer":"^13","@semantic-release/github":"^12","@semantic-release/npm":"^13","@semantic-release/release-notes-generator":"^14","@types/bun":"latest","@types/node":"^22","@types/yargs":"^17.0.35","@typescript/native-preview":"^7.0.0-dev.20260707.2","@vitest/coverage-istanbul":"4.1.10","semantic-release":"^24",typescript:"^5.6",vitest:"4.1.10"},bin:{slack:"./ts/cli.ts"},exports:{"./worker":"./worker/src/index.ts","./package.json":"./package.json"},description:"Slack CLI — read activity/news/mentions, search, send messages with a confirm-hash safety gate.",engines:{node:">=18"},files:["ts/","locales/","dist/","worker/src/","README.md","SKILL.md","LICENSE"],homepage:"https://github.com/snomiao/slack-term",keywords:["slack","cli","messaging","agent","terminal"],license:"MIT",overrides:{"@semantic-release/npm":"^13","@semantic-release/github":"^12"},publishConfig:{access:"public",bin:{slack:"./dist/cli.js"}},scripts:{dev:"bun run ts/cli.ts",build:"bun build ts/cli.ts --target=node --outfile dist/cli.js --minify",typecheck:"tsgo --noEmit","typecheck:tsc":"tsc --noEmit","typecheck:worker":"bun install --cwd worker --frozen-lockfile && tsgo --noEmit -p worker",test:"bun test tests/ --timeout 90000 --parallel=2","test:watch":"bun test tests/ --timeout 90000 --isolate --watch","test:parity":"bash tests/parity.sh",record:"bun run tests/record.ts",anonymize:"bun run tests/anonymize.ts","scan-pii":"bash .githooks/pre-commit","scan-pii:all":"bash .githooks/pre-commit --all",prepare:"git config core.hooksPath .githooks 2>/dev/null || true",prepublishOnly:"bun run scan-pii:all && bun run typecheck && bun run build","test:coverage":"vitest run --coverage --exclude tests/parity.test.ts --no-file-parallelism"},type:"module",dependencies:{yargs:"^18.0.0"},peerDependencies:{"@cloudflare/workers-types":"^4"},peerDependenciesMeta:{"@cloudflare/workers-types":{optional:!0}}};var fw="4.105.0",pw="2026-07-01",wa="slack-term-relay",hw="https://github.com/snomiao/slack-term/blob/main/worker/README.md";function gw(e){return/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(e)}function mw(e){return uw(Gd(e)).toLowerCase().replace(/[^a-z0-9-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,63).replace(/-+$/,"")||wa}function yw(e,t={}){let n={name:e,private:!0,type:"module",scripts:{deploy:"wrangler deploy",dev:"wrangler dev --local"},dependencies:{"slack-term":t.slackTerm??`^${Yd.version}`},devDependencies:{wrangler:fw},engines:{node:">=22"}};return{"package.json":`${JSON.stringify(n,null,2)}
254
+ `),(y,w,x)=>{let D=JSON.parse(w);if(y==="hello")r.hello({...D,via:"sse"});else if(y==="bell"&&x!==void 0)r.bell(Number(x),D)})}}catch(d){if(o.aborted)return;if(c)throw Error(`relay: no data for ${Math.round(n/1000)}s`);throw d}finally{clearTimeout(u),o.removeEventListener("abort",l)}}}import{existsSync as aw,mkdirSync as cw,writeFileSync as lw}from"node:fs";import{basename as uw,join as ba,resolve as Gd}from"node:path";import{randomBytes as dw}from"node:crypto";var Yd={name:"slack-term",version:"1.51.2",repository:{type:"git",url:"https://github.com/snomiao/slack-term.git"},bugs:{url:"https://github.com/snomiao/slack-term/issues"},devDependencies:{"@semantic-release/commit-analyzer":"^13","@semantic-release/github":"^12","@semantic-release/npm":"^13","@semantic-release/release-notes-generator":"^14","@types/bun":"latest","@types/node":"^22","@types/yargs":"^17.0.35","@typescript/native-preview":"^7.0.0-dev.20260707.2","@vitest/coverage-istanbul":"4.1.10","semantic-release":"^24",typescript:"^5.6",vitest:"4.1.10"},bin:{slack:"./ts/cli.ts"},exports:{"./worker":"./worker/src/index.ts","./package.json":"./package.json"},description:"Slack CLI — read activity/news/mentions, search, send messages with a confirm-hash safety gate.",engines:{node:">=18"},files:["ts/","locales/","dist/","worker/src/","README.md","SKILL.md","LICENSE"],homepage:"https://github.com/snomiao/slack-term",keywords:["slack","cli","messaging","agent","terminal"],license:"MIT",overrides:{"@semantic-release/npm":"^13","@semantic-release/github":"^12"},publishConfig:{access:"public",bin:{slack:"./dist/cli.js"}},scripts:{dev:"bun run ts/cli.ts",build:"bun build ts/cli.ts --target=node --outfile dist/cli.js --minify",typecheck:"tsgo --noEmit","typecheck:tsc":"tsc --noEmit","typecheck:worker":"bun install --cwd worker --frozen-lockfile && tsgo --noEmit -p worker",test:"bun test tests/ --timeout 90000 --parallel=2","test:watch":"bun test tests/ --timeout 90000 --isolate --watch","test:parity":"bash tests/parity.sh",record:"bun run tests/record.ts",anonymize:"bun run tests/anonymize.ts","scan-pii":"bash .githooks/pre-commit","scan-pii:all":"bash .githooks/pre-commit --all",prepare:"git config core.hooksPath .githooks 2>/dev/null || true",prepublishOnly:"bun run scan-pii:all && bun run typecheck && bun run build","test:coverage":"vitest run --coverage --exclude tests/parity.test.ts --no-file-parallelism"},type:"module",dependencies:{yargs:"^18.0.0"},peerDependencies:{"@cloudflare/workers-types":"^4"},peerDependenciesMeta:{"@cloudflare/workers-types":{optional:!0}}};var fw="4.105.0",pw="2026-07-01",wa="slack-term-relay",hw="https://github.com/snomiao/slack-term/blob/main/worker/README.md";function gw(e){return/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(e)}function mw(e){return uw(Gd(e)).toLowerCase().replace(/[^a-z0-9-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,63).replace(/-+$/,"")||wa}function yw(e,t={}){let n={name:e,private:!0,type:"module",scripts:{deploy:"wrangler deploy",dev:"wrangler dev --local"},dependencies:{"slack-term":t.slackTerm??`^${Yd.version}`},devDependencies:{wrangler:fw},engines:{node:">=22"}};return{"package.json":`${JSON.stringify(n,null,2)}
255
255
  `,"wrangler.jsonc":`// slack-term relay: Slack Events API -> doorbells for \`slack stream\`.
256
256
  // Secrets (not in this file): wrangler secret put SLACK_SIGNING_SECRET / RELAY_TOKEN / SLACK_TEAM_ID (optional)
257
257
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slack-term",
3
- "version": "1.51.1",
3
+ "version": "1.51.2",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/snomiao/slack-term.git"
package/worker/src/hub.ts CHANGED
@@ -27,7 +27,8 @@
27
27
  import { type Doorbell as Bell, safeEqual } from "./slack.ts";
28
28
 
29
29
  export type { Bell };
30
- export type Row = { seq: number; channel: string; ts: string; thread_ts: string | null; self: string | null };
30
+ export type Row = { seq: number; channel: string; ts: string; thread_ts: string | null; self: string | null;
31
+ kind?: string | null; user?: string | null; rid?: string | null };
31
32
  export type Hello = { seq: number; gap: boolean; retention_sec: number; epoch: string };
32
33
 
33
34
  /** Per-socket state, kept with serializeAttachment (2 KiB max; this is ~100 B). */
@@ -117,6 +118,7 @@ export function bellOf(r: Row): Bell {
117
118
  channel: r.channel, ts: r.ts,
118
119
  ...(r.thread_ts ? { thread_ts: r.thread_ts } : {}),
119
120
  ...(r.self ? { self: r.self } : {}),
121
+ ...(r.kind === "reaction" ? { kind: "reaction" as const, ...(r.user ? { user: r.user } : {}), ...(r.rid ? { rid: r.rid } : {}) } : {}),
120
122
  };
121
123
  }
122
124
 
@@ -129,12 +129,26 @@ export class Relay extends DurableObject<Env> {
129
129
  ctx.storage.sql.exec(`CREATE TABLE IF NOT EXISTS bells (
130
130
  seq INTEGER PRIMARY KEY AUTOINCREMENT,
131
131
  channel TEXT NOT NULL, ts TEXT NOT NULL, thread_ts TEXT,
132
- at INTEGER NOT NULL,
133
- UNIQUE (channel, ts)
132
+ at INTEGER NOT NULL, self TEXT, kind TEXT, user TEXT, rid TEXT NOT NULL DEFAULT '',
133
+ UNIQUE (channel, ts, rid)
134
134
  )`);
135
135
  // Added later: a relay deployed before it gets the column on its next start.
136
- const cols = ctx.storage.sql.exec<{ name: string }>("PRAGMA table_info(bells)").toArray().map((c) => c.name);
136
+ let cols = ctx.storage.sql.exec<{ name: string }>("PRAGMA table_info(bells)").toArray().map((c) => c.name);
137
137
  if (!cols.includes("self")) ctx.storage.sql.exec("ALTER TABLE bells ADD COLUMN self TEXT");
138
+ // Reaction bells (11-05): UNIQUE (channel, ts) swallowed every reaction on a message that had already rung (an
139
+ // ask's pills), and kind/user were not stored. SQLite cannot change a UNIQUE constraint: rebuild, keeping seq.
140
+ cols = ctx.storage.sql.exec<{ name: string }>("PRAGMA table_info(bells)").toArray().map((c) => c.name);
141
+ if (!cols.includes("rid")) {
142
+ ctx.storage.sql.exec(`CREATE TABLE bells_v2 (
143
+ seq INTEGER PRIMARY KEY AUTOINCREMENT,
144
+ channel TEXT NOT NULL, ts TEXT NOT NULL, thread_ts TEXT,
145
+ at INTEGER NOT NULL, self TEXT, kind TEXT, user TEXT, rid TEXT NOT NULL DEFAULT '',
146
+ UNIQUE (channel, ts, rid)
147
+ )`);
148
+ ctx.storage.sql.exec("INSERT INTO bells_v2 (seq, channel, ts, thread_ts, at, self) SELECT seq, channel, ts, thread_ts, at, self FROM bells");
149
+ ctx.storage.sql.exec("DROP TABLE bells");
150
+ ctx.storage.sql.exec("ALTER TABLE bells_v2 RENAME TO bells");
151
+ }
138
152
  ctx.storage.sql.exec("CREATE TABLE IF NOT EXISTS meta (k TEXT PRIMARY KEY, v TEXT NOT NULL)");
139
153
  ctx.storage.sql.exec("INSERT OR IGNORE INTO meta (k, v) VALUES ('epoch', ?)", crypto.randomUUID());
140
154
  this.epoch = ctx.storage.sql.exec<{ v: string }>("SELECT v FROM meta WHERE k = 'epoch'").one().v;
@@ -144,8 +158,9 @@ export class Relay extends DurableObject<Env> {
144
158
  async ring(bell: Doorbell): Promise<void> {
145
159
  const now = Date.now();
146
160
  const rows = this.ctx.storage.sql.exec<{ seq: number }>(
147
- "INSERT OR IGNORE INTO bells (channel, ts, thread_ts, self, at) VALUES (?, ?, ?, ?, ?) RETURNING seq",
148
- bell.channel, bell.ts, bell.thread_ts ?? null, bell.self ?? null, now,
161
+ "INSERT OR IGNORE INTO bells (channel, ts, thread_ts, self, kind, user, rid, at) VALUES (?, ?, ?, ?, ?, ?, ?, ?) RETURNING seq",
162
+ bell.channel, bell.ts, bell.thread_ts ?? null, bell.self ?? null, bell.kind ?? null, bell.user ?? null,
163
+ bell.kind === "reaction" ? `r:${bell.rid ?? now}` : "", now,
149
164
  ).toArray();
150
165
  const seq = rows[0]?.seq;
151
166
  if (seq === undefined) return; // a retry of a bell we already have
@@ -182,7 +197,7 @@ export class Relay extends DurableObject<Env> {
182
197
 
183
198
  private readonly store: hub.Store = {
184
199
  rowsAfter: (seq, limit) => this.ctx.storage.sql.exec<hub.Row>(
185
- "SELECT seq, channel, ts, thread_ts, self FROM bells WHERE seq > ? ORDER BY seq LIMIT ?", seq, limit,
200
+ "SELECT seq, channel, ts, thread_ts, self, kind, user, rid FROM bells WHERE seq > ? ORDER BY seq LIMIT ?", seq, limit,
186
201
  ).toArray(),
187
202
  };
188
203
 
@@ -16,6 +16,9 @@ export type Doorbell = {
16
16
  * ids: which emoji is read from Slack by whoever needs it. */
17
17
  kind?: "reaction";
18
18
  user?: string;
19
+ /** Reaction bells only: the event's event_ts. The relay keeps one bell per channel+ts+rid, so a reaction on a
20
+ * message that already rang still rings, and a Slack retry of the same event does not ring twice. */
21
+ rid?: string;
19
22
  };
20
23
 
21
24
  /** Slack rejects replays older than five minutes; so do we. */
@@ -122,7 +125,8 @@ export function doorbellOf(payload: unknown, teamId?: string): Doorbell | null {
122
125
  const { channel, ts } = item;
123
126
  if (typeof channel !== "string" || !CHANNEL.test(channel) || typeof ts !== "string" || !TS.test(ts)) return null;
124
127
  const self = selfPost(p, e);
125
- return { channel, ts, kind: "reaction", ...(typeof e.user === "string" && USER.test(e.user) ? { user: e.user } : {}), ...(self ? { self } : {}) };
128
+ const rid = typeof e.event_ts === "string" && TS.test(e.event_ts) ? e.event_ts : undefined;
129
+ return { channel, ts, kind: "reaction", ...(typeof e.user === "string" && USER.test(e.user) ? { user: e.user } : {}), ...(self ? { self } : {}), ...(rid ? { rid } : {}) };
126
130
  }
127
131
  if (e.type !== "message" && e.type !== "app_mention") return null;
128
132
  if (typeof e.subtype === "string" && SKIP_SUBTYPES.has(e.subtype)) return null;