slack-term 1.21.1 → 1.21.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 +13 -15
- package/package.json +1 -1
- package/ts/auth.ts +1 -1
- package/ts/profiles.ts +4 -4
- package/ts/slack.ts +5 -6
package/dist/cli.js
CHANGED
|
@@ -84,21 +84,20 @@ ${V.join(`
|
|
|
84
84
|
`)}function K6(D){let u={};for(let F of D.split(`
|
|
85
85
|
`)){let E=F.trim();if(!E||E.startsWith("#"))continue;let C=E.indexOf("=");if(C===-1)continue;let B=E.slice(0,C).trim(),$=E.slice(C+1).trim();if($.startsWith('"')&&$.endsWith('"')||$.startsWith("'")&&$.endsWith("'"))$=$.slice(1,-1);u[B]=$}return u}function A8(D){if(!GF(D))return{};try{return K6($0(D,"utf8"))}catch{return{}}}function B0(D,u){let F=z0(),E=D;while(!0){for(let $ of[Xu(E,".slack-term",".env.local"),Xu(E,".env.local")]){let G=A8($);for(let z of u)if(G[z])return G[z]}if(E===F)break;let B=G0(E);if(B===E)break;E=B}let C=A8(Xu(F,".slack-term",".env.local"));for(let B of u)if(C[B])return C[B];return}function T8(){let D=xu(),u=_u(zF()),F=_u(ZF()),E=u??F;return Object.entries(D.profiles).map(([C,B])=>({name:C,profile:B,current:C===E}))}function R8(D){let u=xu();if(!(D in u.profiles))throw Error(`Profile not found: ${D}`);delete u.profiles[D],M6(u)}function S8(D,u=!1){let F=xu();if(!(D in F.profiles))throw Error(`Profile not found: ${D}`);if(u)j8(ZF(),D);else j8(zF(),D)}function Z0(D){let F=xu().profiles,E=Object.keys(F);if(D){let Z=F[D];if(!Z)throw Error(`Workspace "${D}" not found. Available: ${E.join(", ")||"(none)"}`);return Z.token}if(process.env.SLACK_TOKEN)return process.env.SLACK_TOKEN;let C=process.env.SLACK_BOT_TOKEN??process.env.SLACK_MCP_XOXP_TOKEN;if(C&&E.length===0)return C;if(C&&E.length>0){let Z=B0(process.cwd(),["SLACK_BOT_TOKEN","SLACK_MCP_XOXP_TOKEN"]);if(C!==Z){if(!globalThis.__slackEnvWarnShown)globalThis.__slackEnvWarnShown=!0,console.error(`Warning: SLACK_MCP_XOXP_TOKEN / SLACK_BOT_TOKEN is set but workspace profiles exist — using profiles.
|
|
86
86
|
`+" Migrate to SLACK_TOKEN=... or remove from your shell config.")}}let B=B0(process.cwd(),["SLACK_TOKEN"]);if(B)return B;let $=process.env.SLACK_WORKSPACE;if($){let Z=F[$];if(!Z)throw Error(`Workspace "${$}" not found. Available: ${E.join(", ")||"(none)"}`);return Z.token}let G=_u(zF());if(G){let Z=F[G];if(!Z)throw Error(`Workspace "${G}" (from .slack-cli/workspace) not found in profiles.`);return Z.token}let z=_u(ZF());if(z){let Z=F[z];if(!Z)throw Error(`Workspace "${z}" (from ~/.slack-cli/workspace) not found in profiles.`);return Z.token}if(E.length>0)throw Error(`Workspace not selected (${E.join(", ")} available).
|
|
87
|
-
Select locally: slack
|
|
88
|
-
Select globally: slack
|
|
87
|
+
Select locally: slack auth use <name> (writes .slack-cli/workspace)
|
|
88
|
+
Select globally: slack auth use -g <name> (writes ~/.slack-cli/workspace)`);throw Error(`No Slack token found.
|
|
89
89
|
Run one of:
|
|
90
90
|
`+` slack auth token — paste an existing xoxp-/xoxb- token
|
|
91
91
|
`+` slack auth chrome — import from Chrome browser (macOS)
|
|
92
92
|
`+` slack auth app — guided Slack app creation
|
|
93
93
|
`+" Or set SLACK_TOKEN=xoxp-... in .slack-term/.env.local")}function J0(){let D=process.env.SLACK_BOT_TOKEN;return D&&D.startsWith("xoxb-")?D:void 0}function P8(D){let F=xu().profiles;if(D)return F[D]?.cookie;if(process.env.SLACK_COOKIE)return process.env.SLACK_COOKIE;if(process.env.SLACK_MCP_XOXD_COOKIE)return process.env.SLACK_MCP_XOXD_COOKIE;if(process.env.SLACK_MCP_XOXP_TOKEN&&Object.keys(F).length===0)return process.env.SLACK_MCP_XOXD_COOKIE;let C=B0(process.cwd(),["SLACK_COOKIE"]);if(C)return C;let B=process.env.SLACK_WORKSPACE;if(B)return F[B]?.cookie;let $=_u(zF());if($)return F[$]?.cookie;let G=_u(ZF());if(G)return F[G]?.cookie;return}class lD extends Error{retryAfter;constructor(D){super(`Slack rate limited — retry after ${D}s`);this.name="RateLimitError",this.retryAfter=D}}function X0(){return(process.env.SLACK_API_BASE??"https://slack.com/api").replace(/\/$/,"")}async function w8(D,u,F,E){let C={};if(E)C.Cookie=`d=${E}`;let B=await fetch(`${X0()}/${u}`,{...F,headers:{...F.headers??{},Authorization:`Bearer ${D}`,...C}});if(B.status===429){let G=parseInt(B.headers.get("retry-after")??"60",10);throw new lD(isNaN(G)?60:G)}let $=await B.json();if($.ok!==!0){let G=$.error??"unknown";if(G==="ratelimited")throw new lD(60);if(G==="invalid_auth"&&D.startsWith("xoxc-")&&!E)throw Error(`Desktop app token (xoxc-) is not accepted by the public Slack API.
|
|
94
94
|
Replace it with an xoxp- user token:
|
|
95
|
-
slack
|
|
95
|
+
slack auth token`);if((G==="missing_scope"||G==="cannot_dm_bot")&&D.startsWith("xoxb-"))throw Error(`Slack error on ${u}: ${G} — ${G==="cannot_dm_bot"?`you're authenticated as a bot, so "@you" is the bot itself and it can't DM itself`:"this bot token lacks the user scope this action needs"}.
|
|
96
96
|
Tailing/DMing people and listing users need a user session, not a bot token.
|
|
97
|
-
Switch workspace: slack
|
|
98
|
-
Or import your Slack desktop session: slack
|
|
99
|
-
Import from Slack desktop: slack
|
|
100
|
-
|
|
101
|
-
`+"Get xoxd from browser: DevTools → Application → Cookies → slack.com → d");throw Error(`Slack error on ${u}: ${G}`)}return $}function j6(D,u,F={},E){let C=new URLSearchParams({token:D,...F});return _6(D,u,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:C.toString()},E)}function OD(D,u,F,E){let C=new URLSearchParams(F).toString();return w8(D,`${u}?${C}`,{method:"GET"},E)}function A6(D,u,F){return w8(D,u,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(F)})}async function JF(D){let u=await OD(D,"auth.test",{});return{team:u.team??"",teamId:u.team_id??"",url:u.url??"",user:u.user??"",userId:u.user_id??""}}async function x8(D){let u=await fetch(`${X0()}/auth.test`,{method:"GET",headers:{Authorization:`Bearer ${D}`}});if(u.status===429){let C=parseInt(u.headers.get("retry-after")??"60",10);throw new lD(isNaN(C)?60:C)}let F=u.headers.get("x-oauth-scopes")??"",E=await u.json();if(E.ok!==!0)throw Error(`Slack error on auth.test: ${E.error??"unknown"}`);return{userId:E.user_id??"",botId:E.bot_id??"",team:E.team??"",url:E.url??"",scopes:F.split(",").map((C)=>C.trim()).filter(Boolean)}}async function I8(D,u){let F=await OD(D,"bots.info",{bot:u});return{appId:F.bot?.app_id??"",userId:F.bot?.user_id??""}}async function Q0(D,u,F=20,E,C){let B={channel:u,limit:String(F)};if(E!==void 0)B.oldest=E;if(C!==void 0)B.cursor=C;return OD(D,"conversations.history",B)}async function b8(D,u,F,E=50){return OD(D,"conversations.replies",{channel:u,ts:F,limit:String(E)})}async function L6(D,u){let E=(await A6(D,"conversations.open",{users:u})).channel?.id;if(!E)throw Error(`Failed to open DM with user ${u}`);return E}function Iu(D){return D.toLowerCase().replace(/[-_\s]/g,"")}function Y0(D){let u=D.match(/(?:app\.slack\.com\/client\/T[A-Za-z0-9]+|[A-Za-z0-9-]+\.slack\.com\/archives)\/([A-Za-z0-9]+)(?:\/p(\d{10})(\d{6}))?/);if(!u)return;let F=u[1],E=u[2]&&u[3]?`${u[2]}.${u[3]}`:void 0,C=D.indexOf("?"),B=C>=0?new URLSearchParams(D.slice(C)):void 0,$=B?.get("thread_ts")??void 0,Z={channel:B?.get("cid")??void 0??F};if(E)Z.ts=E;if($)Z.threadTs=$;return Z}function N6(D){return Y0(D)?.channel}async function f8(D,u,F){let E=N6(u);if(E)return E;if(!u.startsWith("@")&&!u.startsWith("#")){if(/^[A-Za-z0-9]{9,}$/.test(u))return u;throw Error(`Target must start with # or @ (or be a Slack URL/ID), got: ${u}`)}let C=u.startsWith("@"),B=u.slice(1),$=Iu(B);if(C){let Z=await OD(D,"auth.test",{},F);if($==="you"||$==="me"||Iu(Z.user??"")===$){let V=Z.user_id;if(!V)throw Error("auth.test did not return user_id");let M=((await OD(D,"conversations.list",{types:"im",limit:"200"},F)).channels??[]).find((_)=>_.user===V);if(M?.id)return String(M.id);return L6(D,V)}let J="",Q="";while(!0){let V={limit:"200"};if(Q)V.cursor=Q;let W=await OD(D,"users.list",V,F);for(let M of W.members??[]){let _=Iu(M.name??""),N=Iu(M.real_name??""),k=Iu(M.profile?.display_name??"");if(_===$||N===$||k&&k===$){J=M.id??"";break}}if(J)break;if(Q=W.response_metadata?.next_cursor??"",!Q)break}if(!J)throw Error(`User not found: ${u}`);let O="";while(!0){let V={types:"im",limit:"200"};if(O)V.cursor=O;let W=await OD(D,"conversations.list",V,F);for(let M of W.channels??[])if(M.user===J)return String(M.id??"");if(O=W.response_metadata?.next_cursor??"",!O)break}throw Error(`No existing DM with ${u} (${J}). Open it once in Slack first.`)}let G=B.toLowerCase(),z="";while(!0){let Z={limit:"200",types:"public_channel,private_channel",exclude_archived:"true"};if(z)Z.cursor=z;let Y=await OD(D,"conversations.list",Z,F);for(let J of Y.channels??[])if(String(J.name??"").toLowerCase()===G)return String(J.id??"");if(z=Y.response_metadata?.next_cursor??"",!z)break}throw Error(`channel not found: ${u}`)}async function XF(D,u){try{let F=await OD(D,"users.info",{user:u}),E=F.user?.profile?.display_name,C=E&&E.length>0?E:F.user?.real_name??F.user?.name??u,B=F.user?.name??u;return[C,B]}catch{return[u,u]}}async function QF(D,u){try{let F=await OD(D,"users.info",{user:u}),E=F.user?.profile?.display_name;if(E&&E.length>0)return E;return F.user?.real_name??F.user?.name??u}catch{return u}}async function YF(D){let u=[],F="";while(!0){let E={limit:"200"};if(F)E.cursor=F;let C=await OD(D,"users.list",E);if(u.push(...C.members??[]),F=C.response_metadata?.next_cursor??"",!F)break}return{members:u}}async function W0(D,u){let F=[],E="";while(!0){let C={channel:u,limit:"200"};if(E)C.cursor=E;let B=await OD(D,"conversations.members",C);if(F.push(...B.members??[]),E=B.response_metadata?.next_cursor??"",!E)break}return F}async function O0(D,u){return OD(D,"users.info",{user:u})}async function h8(D,u){return OD(D,"conversations.info",{channel:u})}async function v8(D,u){let F=await j6(D,"client.boot",{},u),E=typeof F.url==="string"?F.url:void 0;if(!E){let B=Object.keys(F).join(", ");throw Error(`client.boot did not return a WebSocket URL. Keys: ${B}`)}let C=F.self&&typeof F.self==="object"&&!Array.isArray(F.self)?F.self:{};return{wsUrl:E,selfId:typeof C.id==="string"?C.id:""}}var T6=["chat:write","im:write"],R6=["im:history","im:read"];async function V0(D){let{userId:u,botId:F,team:E,scopes:C}=await x8(D),B="";if(F)try{B=(await I8(D,F)).appId}catch{B=""}let $=(Z)=>C.includes(Z),G=T6.filter((Z)=>!$(Z)),z=R6.filter((Z)=>!$(Z));return{canSend:G.length===0,canReceiveReplies:z.length===0,ok:G.length===0&&z.length===0,botUserId:u,appId:B,team:E,grantedScopes:C,missingSendScopes:G,missingReplyScopes:z}}function p8(D){return["",` Bot user: ${D.botUserId||"(unknown)"}`,` App ID: ${D.appId||"(unknown — bots.info unavailable)"}`,` Granted scopes: ${D.grantedScopes.join(", ")||"(none reported)"}`]}function H0(D,u=!1){let F=D.appId?`https://api.slack.com/apps/${D.appId}`:"https://api.slack.com/apps";if(D.ok){let C=["✓ Bot DM scopes are complete (send + read replies)."," Two-way replies also require the Messages Tab, which is not API-detectable.",' If recipients see "Sending messages to this app is turned off", enable it:',` ${F} → App Home → "Messages Tab" ON + "Allow users to send messages".`];return u?C:[...C,...p8(D)]}let E=[];if(!D.canSend)E.push(`⚠ This bot CANNOT send DMs — missing scope(s): ${D.missingSendScopes.join(", ")}.`);else E.push("⚠ This bot can send DMs, but the user's replies will NOT be readable.");return E.push(" Make escalation DMs two-way:"),E.push(` 1. ${F} → App Home → enable "Messages Tab" and check`),E.push(' "Allow users to send Slash commands and messages".'),E.push(` 2. OAuth & Permissions → add Bot Token Scopes: ${[...D.missingSendScopes,...D.missingReplyScopes].join(", ")}`),E.push(" 3. Reinstall the app to apply the new scopes."),u?E:[...E,...p8(D)]}import{createInterface as Yu}from"node:readline/promises";import{join as A0}from"node:path";import{homedir as c6}from"node:os";import{existsSync as q0,readFileSync as U0,writeFileSync as m8,mkdirSync as c8}from"node:fs";import{homedir as S6}from"node:os";import{join as M0,dirname as g8}from"node:path";function k8(){return process.env.HOME||S6()}function d8(){return M0(k8(),".config","slack-cli","profiles.json")}function P6(){return M0(process.cwd(),".slack-cli","workspace")}function w6(){return M0(k8(),".slack-cli","workspace")}function K0(){let D=d8();if(!q0(D))return{profiles:{}};return JSON.parse(U0(D,"utf8"))}function i8(D){let u=d8();c8(g8(u),{recursive:!0}),m8(u,JSON.stringify(D,null,2)+`
|
|
97
|
+
Switch workspace: slack auth ls then slack auth use <name>
|
|
98
|
+
Or import your Slack desktop session: slack auth login`);throw Error(`Slack error on ${u}: ${G}`)}return $}async function _6(D,u,F,E){let C={};if(E)C.Cookie=`d=${E}`;let B=await fetch(`${X0()}/${u}`,{...F,headers:{...F.headers??{},Authorization:`Bearer ${D}`,...C}});if(B.status===429){let G=parseInt(B.headers.get("retry-after")??"60",10);throw new lD(isNaN(G)?60:G)}let $=await B.json();if($.ok!==!0){let G=$.error??"unknown";if(G==="ratelimited")throw new lD(60);if((G==="invalid_auth"||G==="not_authed")&&!D.startsWith("xoxc-"))throw Error(`The draft API requires a desktop app session token (xoxc-).
|
|
99
|
+
Import from Slack desktop: slack auth login`);if((G==="invalid_auth"||G==="not_authed")&&!E)throw Error(`drafts.list also requires the xoxd session cookie.
|
|
100
|
+
Attach it with: slack auth chrome (macOS) or slack auth firefox`);throw Error(`Slack error on ${u}: ${G}`)}return $}function j6(D,u,F={},E){let C=new URLSearchParams({token:D,...F});return _6(D,u,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:C.toString()},E)}function OD(D,u,F,E){let C=new URLSearchParams(F).toString();return w8(D,`${u}?${C}`,{method:"GET"},E)}function A6(D,u,F){return w8(D,u,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(F)})}async function JF(D){let u=await OD(D,"auth.test",{});return{team:u.team??"",teamId:u.team_id??"",url:u.url??"",user:u.user??"",userId:u.user_id??""}}async function x8(D){let u=await fetch(`${X0()}/auth.test`,{method:"GET",headers:{Authorization:`Bearer ${D}`}});if(u.status===429){let C=parseInt(u.headers.get("retry-after")??"60",10);throw new lD(isNaN(C)?60:C)}let F=u.headers.get("x-oauth-scopes")??"",E=await u.json();if(E.ok!==!0)throw Error(`Slack error on auth.test: ${E.error??"unknown"}`);return{userId:E.user_id??"",botId:E.bot_id??"",team:E.team??"",url:E.url??"",scopes:F.split(",").map((C)=>C.trim()).filter(Boolean)}}async function I8(D,u){let F=await OD(D,"bots.info",{bot:u});return{appId:F.bot?.app_id??"",userId:F.bot?.user_id??""}}async function Q0(D,u,F=20,E,C){let B={channel:u,limit:String(F)};if(E!==void 0)B.oldest=E;if(C!==void 0)B.cursor=C;return OD(D,"conversations.history",B)}async function b8(D,u,F,E=50){return OD(D,"conversations.replies",{channel:u,ts:F,limit:String(E)})}async function L6(D,u){let E=(await A6(D,"conversations.open",{users:u})).channel?.id;if(!E)throw Error(`Failed to open DM with user ${u}`);return E}function Iu(D){return D.toLowerCase().replace(/[-_\s]/g,"")}function Y0(D){let u=D.match(/(?:app\.slack\.com\/client\/T[A-Za-z0-9]+|[A-Za-z0-9-]+\.slack\.com\/archives)\/([A-Za-z0-9]+)(?:\/p(\d{10})(\d{6}))?/);if(!u)return;let F=u[1],E=u[2]&&u[3]?`${u[2]}.${u[3]}`:void 0,C=D.indexOf("?"),B=C>=0?new URLSearchParams(D.slice(C)):void 0,$=B?.get("thread_ts")??void 0,Z={channel:B?.get("cid")??void 0??F};if(E)Z.ts=E;if($)Z.threadTs=$;return Z}function N6(D){return Y0(D)?.channel}async function f8(D,u,F){let E=N6(u);if(E)return E;if(!u.startsWith("@")&&!u.startsWith("#")){if(/^[A-Za-z0-9]{9,}$/.test(u))return u;throw Error(`Target must start with # or @ (or be a Slack URL/ID), got: ${u}`)}let C=u.startsWith("@"),B=u.slice(1),$=Iu(B);if(C){let Z=await OD(D,"auth.test",{},F);if($==="you"||$==="me"||Iu(Z.user??"")===$){let V=Z.user_id;if(!V)throw Error("auth.test did not return user_id");let M=((await OD(D,"conversations.list",{types:"im",limit:"200"},F)).channels??[]).find((_)=>_.user===V);if(M?.id)return String(M.id);return L6(D,V)}let J="",Q="";while(!0){let V={limit:"200"};if(Q)V.cursor=Q;let W=await OD(D,"users.list",V,F);for(let M of W.members??[]){let _=Iu(M.name??""),N=Iu(M.real_name??""),k=Iu(M.profile?.display_name??"");if(_===$||N===$||k&&k===$){J=M.id??"";break}}if(J)break;if(Q=W.response_metadata?.next_cursor??"",!Q)break}if(!J)throw Error(`User not found: ${u}`);let O="";while(!0){let V={types:"im",limit:"200"};if(O)V.cursor=O;let W=await OD(D,"conversations.list",V,F);for(let M of W.channels??[])if(M.user===J)return String(M.id??"");if(O=W.response_metadata?.next_cursor??"",!O)break}throw Error(`No existing DM with ${u} (${J}). Open it once in Slack first.`)}let G=B.toLowerCase(),z="";while(!0){let Z={limit:"200",types:"public_channel,private_channel",exclude_archived:"true"};if(z)Z.cursor=z;let Y=await OD(D,"conversations.list",Z,F);for(let J of Y.channels??[])if(String(J.name??"").toLowerCase()===G)return String(J.id??"");if(z=Y.response_metadata?.next_cursor??"",!z)break}throw Error(`channel not found: ${u}`)}async function XF(D,u){try{let F=await OD(D,"users.info",{user:u}),E=F.user?.profile?.display_name,C=E&&E.length>0?E:F.user?.real_name??F.user?.name??u,B=F.user?.name??u;return[C,B]}catch{return[u,u]}}async function QF(D,u){try{let F=await OD(D,"users.info",{user:u}),E=F.user?.profile?.display_name;if(E&&E.length>0)return E;return F.user?.real_name??F.user?.name??u}catch{return u}}async function YF(D){let u=[],F="";while(!0){let E={limit:"200"};if(F)E.cursor=F;let C=await OD(D,"users.list",E);if(u.push(...C.members??[]),F=C.response_metadata?.next_cursor??"",!F)break}return{members:u}}async function W0(D,u){let F=[],E="";while(!0){let C={channel:u,limit:"200"};if(E)C.cursor=E;let B=await OD(D,"conversations.members",C);if(F.push(...B.members??[]),E=B.response_metadata?.next_cursor??"",!E)break}return F}async function O0(D,u){return OD(D,"users.info",{user:u})}async function h8(D,u){return OD(D,"conversations.info",{channel:u})}async function v8(D,u){let F=await j6(D,"client.boot",{},u),E=typeof F.url==="string"?F.url:void 0;if(!E){let B=Object.keys(F).join(", ");throw Error(`client.boot did not return a WebSocket URL. Keys: ${B}`)}let C=F.self&&typeof F.self==="object"&&!Array.isArray(F.self)?F.self:{};return{wsUrl:E,selfId:typeof C.id==="string"?C.id:""}}var T6=["chat:write","im:write"],R6=["im:history","im:read"];async function V0(D){let{userId:u,botId:F,team:E,scopes:C}=await x8(D),B="";if(F)try{B=(await I8(D,F)).appId}catch{B=""}let $=(Z)=>C.includes(Z),G=T6.filter((Z)=>!$(Z)),z=R6.filter((Z)=>!$(Z));return{canSend:G.length===0,canReceiveReplies:z.length===0,ok:G.length===0&&z.length===0,botUserId:u,appId:B,team:E,grantedScopes:C,missingSendScopes:G,missingReplyScopes:z}}function p8(D){return["",` Bot user: ${D.botUserId||"(unknown)"}`,` App ID: ${D.appId||"(unknown — bots.info unavailable)"}`,` Granted scopes: ${D.grantedScopes.join(", ")||"(none reported)"}`]}function H0(D,u=!1){let F=D.appId?`https://api.slack.com/apps/${D.appId}`:"https://api.slack.com/apps";if(D.ok){let C=["✓ Bot DM scopes are complete (send + read replies)."," Two-way replies also require the Messages Tab, which is not API-detectable.",' If recipients see "Sending messages to this app is turned off", enable it:',` ${F} → App Home → "Messages Tab" ON + "Allow users to send messages".`];return u?C:[...C,...p8(D)]}let E=[];if(!D.canSend)E.push(`⚠ This bot CANNOT send DMs — missing scope(s): ${D.missingSendScopes.join(", ")}.`);else E.push("⚠ This bot can send DMs, but the user's replies will NOT be readable.");return E.push(" Make escalation DMs two-way:"),E.push(` 1. ${F} → App Home → enable "Messages Tab" and check`),E.push(' "Allow users to send Slash commands and messages".'),E.push(` 2. OAuth & Permissions → add Bot Token Scopes: ${[...D.missingSendScopes,...D.missingReplyScopes].join(", ")}`),E.push(" 3. Reinstall the app to apply the new scopes."),u?E:[...E,...p8(D)]}import{createInterface as Yu}from"node:readline/promises";import{join as A0}from"node:path";import{homedir as c6}from"node:os";import{existsSync as q0,readFileSync as U0,writeFileSync as m8,mkdirSync as c8}from"node:fs";import{homedir as S6}from"node:os";import{join as M0,dirname as g8}from"node:path";function k8(){return process.env.HOME||S6()}function d8(){return M0(k8(),".config","slack-cli","profiles.json")}function P6(){return M0(process.cwd(),".slack-cli","workspace")}function w6(){return M0(k8(),".slack-cli","workspace")}function K0(){let D=d8();if(!q0(D))return{profiles:{}};return JSON.parse(U0(D,"utf8"))}function i8(D){let u=d8();c8(g8(u),{recursive:!0}),m8(u,JSON.stringify(D,null,2)+`
|
|
102
101
|
`)}function y8(D){if(!q0(D))return;return U0(D,"utf8").trim()||void 0}function n8(D,u){c8(g8(D),{recursive:!0});let F=q0(D)?U0(D,"utf8"):"",E=F?F.split(`
|
|
103
102
|
`):[];for(let[C,B]of Object.entries(u)){let $=E.findIndex((z)=>{let Z=z.trim();return Z.startsWith(C+"=")||Z.startsWith(C+" =")}),G=`${C}=${B}`;if($!==-1)E[$]=G;else E.push(G)}m8(D,E.join(`
|
|
104
103
|
`).trimEnd()+`
|
|
@@ -108,7 +107,7 @@ Is Slack installed and opened at least once?`);let u=OF(D).filter((G)=>G.endsWit
|
|
|
108
107
|
`)){let G=$.trim();if(G.startsWith("[")){C="",B="";continue}let z=G.indexOf("=");if(z===-1)continue;let Z=G.slice(0,z).trim().toLowerCase(),Y=G.slice(z+1).trim();if(Z==="name")C=Y;if(Z==="path")B=Y.split(/[\\/]/).pop()??Y;if(C&&B)F[B]=C,C="",B=""}}catch{}return F}function u1(){let D=y6();if(!D||!Qu(D))return[];let u=m6(D),F=[],E;try{E=OF(D)}catch{return[]}for(let C of E){let B=ZD(D,C,"cookies.sqlite");if(!Qu(B))continue;let $=ZD(o8(),`slack-firefox-cookies-${Date.now()}-${C}.db`);try{l8(B,$);let{default:G}=wD("bun:sqlite"),z=new G($,{readonly:!0}),Z=z.prepare("SELECT value FROM moz_cookies WHERE name='d' AND host LIKE '%slack%' LIMIT 1").get();if(z.close(),!Z?.value||!Z.value.startsWith("xoxd-"))continue;let Y=u[C]??C;F.push({profileDir:C,profileName:Y,cookie:Z.value})}catch{}finally{try{t8($)}catch{}}}return F}var g6=["search:read","channels:history","groups:history","im:history","mpim:history","channels:read","groups:read","im:read","mpim:read","users:read","chat:write","files:write"],k6=["channels:history","groups:history","im:history","mpim:history","channels:read","groups:read","im:read","mpim:read","users:read","chat:write","files:write"];function HF(D){return D.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function d6(){return JSON.stringify({display_information:{name:"slack-term"},oauth_config:{scopes:{user:g6}},settings:{org_deploy_enabled:!1,socket_mode_enabled:!1,token_rotation_enabled:!1}},null,2)}function i6(){return JSON.stringify({display_information:{name:"slack-term"},features:{bot_user:{display_name:"slack-term",always_online:!1}},oauth_config:{scopes:{bot:k6}},settings:{org_deploy_enabled:!1,socket_mode_enabled:!1,token_rotation_enabled:!1}},null,2)}async function Wu(D,u){return(await D.question(u)).trim()}async function Ou(D,u,F,E){console.error("Verifying token...");let C=await JF(u),B=HF(C.team),$;if(F)$=F;else if(D)$=await Wu(D,`Workspace name [${B}]: `)||B;else $=B;let G=null;if(D){console.log(""),console.log("Where to save the token?"),console.log(" 1) ./.env.local (current directory) [default]"),console.log(" 2) ./.slack-term/.env.local (current directory, slack-specific)"),console.log(" 3) ~/.slack-term/.env.local (global, available everywhere)"),console.log(" 4) profiles.json (multi-workspace: slack auth use)"),console.log("");let z=await Wu(D,"Choice [1/2/3/4, Enter=1]: ");if(z==="2")G=A0(process.cwd(),".slack-term",".env.local");else if(z==="3")G=A0(c6(),".slack-term",".env.local");else if(z==="4")G=null;else G=A0(process.cwd(),".env.local")}if(G===null)if(_0($,{token:u,...C,...E?{cookie:E}:{}}),console.log(`Saved workspace "${$}" to profiles.json: ${C.team} (${C.user})`),process.env.SLACK_MCP_XOXP_TOKEN)console.log(""),console.log("Warning: SLACK_MCP_XOXP_TOKEN is set — it conflicts with profiles."),console.log(" Unset it: unset SLACK_MCP_XOXP_TOKEN (and remove from ~/.zshrc / ~/.bashrc)");else console.log(` Run: slack auth use -g ${$}`);else{let z={SLACK_TOKEN:u};if(E)z.SLACK_COOKIE=E;n8(G,z),console.log(`Saved token to ${G}`),console.log(" SLACK_TOKEN will be picked up automatically in this directory tree.")}return $}async function n6(D){console.error("Scanning Slack desktop app...");let u=await e8();if(u.length===0)console.error("No sessions found. Make sure Slack is installed and you have signed in at least once."),process.exit(1);if(u.length===1&&D){let F=u[0],E=F.teamName??F.teamId;console.log(`Found workspace: ${E}${F.cookie?" (+ xoxd cookie)":""}`),await Ou(D,F.token,HF(E),F.cookie),console.log(""),console.log("Note: desktop app tokens (xoxc-) are internal Slack tokens."),console.log("If API calls fail, replace with an xoxp- token: slack auth login");return}for(let F of u){let E=F.teamName??F.teamId,C=HF(E);_0(C,{token:F.token,team:E,teamId:F.teamId,url:F.url??"",user:"",...F.cookie?{cookie:F.cookie}:{}}),console.log(`Added workspace "${C}": ${E}${F.cookie?" + xoxd cookie":""}`)}if(console.log(""),u.length===1){let F=HF(u[0].teamName??u[0].teamId);console.log(`Run: slack auth use -g ${F}`)}else console.log("Run: slack auth ls then: slack auth use -g <name>");console.log(""),console.log("Note: desktop app tokens (xoxc-) are internal Slack tokens."),console.log("If API calls fail, replace with an xoxp- user token by re-running: slack auth login")}async function F1(D){console.log("Which token type does your app use?"),console.log(""),console.log(" 1) User token (xoxp-) -full access including search [recommended]"),console.log(" 2) Bot token (xoxb-) -search and news unavailable"),console.log("");let F=await Wu(D,"Choice [1/2]: ")==="2"?"bot":"user",E=F==="user"?"xoxp-":"xoxb-",C=F==="user"?"User OAuth Token":"Bot User OAuth Token";console.log(""),console.log("Find your token here:"),console.log(" https://api.slack.com/apps"),console.log(" -> Select your app -> OAuth & Permissions"),console.log(` -> Copy the "${C}" (starts with ${E})`),console.log("");let B=await Wu(D,"Paste your token: ");if(!B)console.error("No token provided."),process.exit(1);if(!B.startsWith(E))console.error(`Expected a ${E} token, got: ${B.slice(0,10)}...`),process.exit(1);await Ou(D,B)}async function qF(D,u){let F=u==="user"?d6():i6(),E=u==="user"?"User OAuth Token (xoxp-...)":"Bot User OAuth Token (xoxb-...)",C=u==="user"?"xoxp-":"xoxb-";if(u==="bot")console.log("Note: bot tokens cannot use search:read (user-only scope)."),console.log(" The news and search commands will not work with a bot token."),console.log("");console.log("Step 1 -Create your Slack app:"),console.log(" Open: https://api.slack.com/apps"),console.log(' Click "Create New App" -> "From a manifest" -> select your workspace'),console.log(" Paste this manifest (JSON tab):"),console.log(""),console.log("-------------------------------------------------------------"),console.log(F),console.log("-------------------------------------------------------------"),console.log(""),console.log('Step 2 -Install: "Install App" -> "Install to Workspace" -> Authorize'),console.log(""),console.log("Step 3 -Copy your token:"),console.log(` OAuth & Permissions -> ${E}`),console.log("");let B=await Wu(D,"Paste your token: ");if(!B)console.error("No token provided."),process.exit(1);if(!B.startsWith(C))console.error(`Expected a ${C} token, got: ${B.slice(0,10)}...`),process.exit(1);await Ou(D,B)}async function E1(D={}){if(process.platform!=="darwin")console.error("Chrome cookie extraction is only supported on macOS."),process.exit(1);let u=WF();if(u.length===0)console.error("No workspaces configured. Run: slack auth login"),process.exit(1);let F;if(D.workspace){if(!u.find((z)=>z.name===D.workspace))console.error(`Workspace "${D.workspace}" not found. Available: ${u.map((z)=>z.name).join(", ")}`),process.exit(1);F=D.workspace}else if(u.length===1)F=u[0].name;else{let G=u.find((z)=>z.current);if(G)F=G.name,console.log(`Using active workspace: ${F}`);else{console.log("Multiple workspaces found. Choose one:"),u.forEach((J,Q)=>console.log(` ${Q+1}) ${J.name} (${J.profile.team})`));let z=Yu({input:process.stdin,output:process.stdout}),Z=(await z.question("Choice: ")).trim();z.close();let Y=parseInt(Z,10)-1;if(isNaN(Y)||Y<0||Y>=u.length)console.error("Invalid choice."),process.exit(1);F=u[Y].name}}if(u.find((G)=>G.name===F).profile.token.startsWith("xoxb-")){console.error(`
|
|
109
108
|
Workspace "${F}" uses a bot token (xoxb-). A Chrome session cookie only
|
|
110
109
|
`+`augments a user session token (xoxc-) — it has no effect on a bot token, so user-level
|
|
111
|
-
`+"actions (tail @user, DMs, listing users) would still fail.");let G=u.filter((z)=>z.profile.token.startsWith("xoxc-")||z.profile.token.startsWith("xoxp-"));if(G.length>0)console.error(` Target a user-token workspace instead: ${G.map((z)=>z.name).join(", ")}`),console.error(` slack auth chrome -w ${G[0].name}`);else console.error(" Import your Slack desktop user session instead: slack
|
|
110
|
+
`+"actions (tail @user, DMs, listing users) would still fail.");let G=u.filter((z)=>z.profile.token.startsWith("xoxc-")||z.profile.token.startsWith("xoxp-"));if(G.length>0)console.error(` Target a user-token workspace instead: ${G.map((z)=>z.name).join(", ")}`),console.error(` slack auth chrome -w ${G[0].name}`);else console.error(" Import your Slack desktop user session instead: slack auth login");process.exit(1)}console.log("Scanning Chrome profiles for Slack session..."),console.log("macOS may show a dialog asking for your login password — click Allow.");let C,B;try{({candidates:C,totalProfiles:B}=D1())}catch(G){console.error(`Failed: ${G instanceof Error?G.message:String(G)}`),process.exit(1)}if(C.length===0)console.error(`No Slack session found in Chrome (scanned ${B} profile${B!==1?"s":""}). Possible reasons:`),console.error(" - You denied the keychain dialog (try running again and click Allow)"),console.error(" - Chrome is not installed or has no Slack session"),console.error(" - You're not logged in to Slack in Chrome"),process.exit(1);let $;if(C.length===1&&B<=1)$=C[0].cookie,console.log(`Found session in Chrome profile: ${C[0].profileName}`);else{let G=C.length===1?`Found 1 Slack session across ${B} Chrome profiles:`:`Found ${C.length} Slack sessions across ${B} Chrome profiles:`;console.log(G),C.forEach((Q,O)=>console.log(` ${O+1}) ${Q.profileName} [${Q.profileDir}]`)),console.log("");let z=Yu({input:process.stdin,output:process.stdout}),Z=C.length===1?" [Enter=1]":"",Y=(await z.question(`Choice [1-${C.length}]${Z}: `)).trim();z.close();let J=Y===""&&C.length===1?0:parseInt(Y,10)-1;if(isNaN(J)||J<0||J>=C.length)console.error("Invalid choice."),process.exit(1);$=C[J].cookie}j0(F,$),console.log(`Saved xoxd cookie to workspace "${F}".`),console.log("RTM WebSocket mode is now available: slack tail @you")}async function C1(D={}){let u=WF();if(u.length===0)console.error("No workspaces configured. Run: slack auth token"),process.exit(1);let F;if(D.workspace){if(!u.find(($)=>$.name===D.workspace))console.error(`Workspace "${D.workspace}" not found. Available: ${u.map(($)=>$.name).join(", ")}`),process.exit(1);F=D.workspace}else if(u.length===1)F=u[0].name;else{let B=u.find(($)=>$.current);if(B)F=B.name,console.log(`Using active workspace: ${F}`);else{console.log("Multiple workspaces found. Choose one:"),u.forEach((Z,Y)=>console.log(` ${Y+1}) ${Z.name} (${Z.profile.team})`));let $=Yu({input:process.stdin,output:process.stdout}),G=(await $.question("Choice: ")).trim();$.close();let z=parseInt(G,10)-1;if(isNaN(z)||z<0||z>=u.length)console.error("Invalid choice."),process.exit(1);F=u[z].name}}console.log("Scanning Firefox profiles for Slack session...");let E=u1();if(E.length===0)console.error("No Slack session found in Firefox. Possible reasons:"),console.error(" - Firefox is not installed"),console.error(" - You are not logged in to Slack in Firefox"),process.exit(1);let C;if(E.length===1)C=E[0].cookie,console.log(`Found session in Firefox profile: ${E[0].profileName}`);else{console.log("Multiple Firefox profiles have a Slack session. Choose one:"),E.forEach((z,Z)=>console.log(` ${Z+1}) ${z.profileName} [${z.profileDir}]`));let B=Yu({input:process.stdin,output:process.stdout}),$=(await B.question("Choice: ")).trim();B.close();let G=parseInt($,10)-1;if(isNaN(G)||G<0||G>=E.length)console.error("Invalid choice."),process.exit(1);C=E[G].cookie}j0(F,C),console.log(`Saved xoxd cookie to workspace "${F}".`),console.log("RTM WebSocket mode is now available: slack tail @you")}async function B1(D={}){if(D.token){await Ou(null,D.token,D.name);return}if(!process.stdin.isTTY){let F=[];for await(let C of process.stdin)F.push(C);let E=Buffer.concat(F).toString("utf8").trim();if(!E)console.error("No token provided on stdin."),process.exit(1);await Ou(null,E,D.name);return}let u=Yu({input:process.stdin,output:process.stdout});try{await F1(u)}finally{u.close()}}async function $1(D={}){let u=Yu({input:process.stdin,output:process.stdout});try{if(D.bot!==void 0){await qF(u,D.bot?"bot":"user");return}console.log("Which token type?"),console.log(" 1) User token (xoxp-) -full access including search [default]"),console.log(" 2) Bot token (xoxb-) -search and news unavailable"),console.log("");let F=await Wu(u,"Choice [1/2, Enter=1]: ");await qF(u,F==="2"?"bot":"user")}finally{u.close()}}async function UF(D={}){let u=WF();if(u.length>0){console.log("Currently logged in:");for(let{name:E,profile:C,current:B}of u)console.log(` ${B?"*":" "} ${E} ${C.team} (${C.user||"unknown"})`);console.log(""),console.log("Adding another workspace:"),console.log("")}if(D.token){await Ou(null,D.token,D.name);return}if(!process.stdin.isTTY){let E=[];for await(let B of process.stdin)E.push(B);let C=Buffer.concat(E).toString("utf8").trim();if(!C)console.error("No token provided on stdin."),process.exit(1);await Ou(null,C,D.name);return}console.log("How would you like to authenticate with Slack?"),console.log(""),console.log(" 1) Slack desktop app - import session token"),console.log(" Reads the xoxc- token directly from the installed app."),console.log(" Token: all platforms | xoxd cookie: macOS only"),console.log(""),console.log(" 2) Connect existing Slack app [recommended if you have one]"),console.log(" Paste a token from an app you already created."),console.log(""),console.log(" 3) Create new Slack app - user token (xoxp-)"),console.log(" Guided setup with manifest. Full access including search."),console.log(""),console.log(" 4) Create new Slack app - bot token (xoxb-)"),console.log(" Bot is invited to channels. Search and news unavailable."),console.log("");let F=Yu({input:process.stdin,output:process.stdout});try{let E=await Wu(F,"Choice [1/2/3/4]: ");if(console.log(""),E==="1")await n6(F);else if(E==="2")await F1(F);else if(E==="3")await qF(F,"user");else if(E==="4")await qF(F,"bot");else console.error(`Invalid choice: "${E}". Enter 1, 2, 3, or 4.`),process.exit(1)}finally{F.close()}}async function MF(D,u,F){let E=u,C=[],B=E,$=0;while(!0){let G=B.indexOf("<@U",$);if(G===-1)break;let z=B.slice(G+2),Z=z.search(/[>|]/),Y=Z===-1?z:z.slice(0,Z);if(Y.length>=9)C.push(Y);$=G+1}for(let G of C){if(!F.has(G))F.set(G,await QF(D,G));let z=F.get(G)??G;E=E.replaceAll(`<@${G}>`,`@${z}`),E=E.replace(new RegExp(`<@${G}\\|[^>]*>`,"g"),`@${z}`)}return E}function KF(D){return D.replace(/<!date\^(\d+)\^[^|>]*(?:\|([^>]*))?>/g,(u,F,E)=>{let C=Number(F);if(!Number.isFinite(C))return E??"date";let B=new Date(C*1000);if(Number.isNaN(B.getTime()))return E??"date";let $=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],G=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],z=$[B.getUTCDay()]??"",Z=G[B.getUTCMonth()]??"";return`${z}, ${Z} ${String(B.getUTCDate()).padStart(2,"0")}, ${B.getUTCFullYear()}`})}function z1(D){let u=typeof D.ts==="string"?D.ts:"",F=typeof D.thread_ts==="string"?D.thread_ts:"";return F!==""&&F!==u}function l6(D){let[u,F="000000"]=D.split("."),E=new Date(Number(u)*1000),C=E.getUTCFullYear(),B=String(E.getUTCMonth()+1).padStart(2,"0"),$=String(E.getUTCDate()).padStart(2,"0"),G=String(E.getUTCHours()).padStart(2,"0"),z=String(E.getUTCMinutes()).padStart(2,"0"),Z=String(E.getUTCSeconds()).padStart(2,"0"),Y=F.padEnd(6,"0").slice(0,6);return`${C}-${B}-${$}T${G}:${z}:${Z}.${Y}`}async function t6(D,u,F){let E=typeof u.ts==="string"?u.ts:"0.000000",C=l6(E),B="?";if(typeof u.user==="string"){let J=u.user,Q="@"+J;if(!F.has(Q)){let[,O]=await XF(D,J);F.set(Q,O)}B=F.get(Q)??J}else if(typeof u.username==="string")B=u.username;let $=typeof u.text==="string"?u.text:"",z=KF(await MF(D,$,F)).split(`
|
|
112
111
|
`),Z=z[0]+(z.length>1?`
|
|
113
112
|
`+z.slice(1).map((J)=>` ${J}`).join(`
|
|
114
113
|
`):""),Y=z1(u)?"↳ ":"";return`${C} ${Y}@${B}: ${Z}`}async function o6(D,u,F,E,C,B,$,G){if(G?.aborted)return;return new Promise((z)=>{let Z=new D(F);G?.addEventListener("abort",()=>{Z.close(),z()},{once:!0}),Z.addEventListener("open",()=>{process.stderr.write(`Connected via RTM (experimental).
|
|
@@ -123,13 +122,12 @@ Use: slack tail "#channel" --me`),process.exit(1);if(!u)console.error("Usage: sl
|
|
|
123
122
|
`);c=!1;for(let oD of r)process.stdout.write(oD+`
|
|
124
123
|
`);if(i)W=!0;w=k?_D:void 0}while(w);O=!1}catch(_){if(_ instanceof lD){console.error(`Warning: Slack rate limit hit — backing off for ${_.retryAfter}s`),await SD.sleep(_.retryAfter*1000);continue}throw _}if(F.exitOnMessage&&W)return;V=SD.now();let M=$-SD.now();if(M<=0)return;await SD.sleep(Math.min(B,M))}}class fu extends Error{retryAfter;constructor(D){super(`Slack rate limited — retry after ${D}s`);this.name="RateLimitError",this.retryAfter=D}}function W1(){return(process.env.SLACK_API_BASE??"https://slack.com/api").replace(/\/$/,"")}async function O1(D,u,F,E){let C={};if(E)C.Cookie=`d=${E}`;let B=await fetch(`${W1()}/${u}`,{...F,headers:{...F.headers??{},Authorization:`Bearer ${D}`,...C}});if(B.status===429){let G=parseInt(B.headers.get("retry-after")??"60",10);throw new fu(isNaN(G)?60:G)}let $=await B.json();if($.ok!==!0){let G=$.error??"unknown";if(G==="ratelimited")throw new fu(60);if(G==="invalid_auth"&&D.startsWith("xoxc-")&&!E)throw Error(`Desktop app token (xoxc-) is not accepted by the public Slack API.
|
|
125
124
|
Replace it with an xoxp- user token:
|
|
126
|
-
slack
|
|
125
|
+
slack auth token`);if((G==="missing_scope"||G==="cannot_dm_bot")&&D.startsWith("xoxb-"))throw Error(`Slack error on ${u}: ${G} — ${G==="cannot_dm_bot"?`you're authenticated as a bot, so "@you" is the bot itself and it can't DM itself`:"this bot token lacks the user scope this action needs"}.
|
|
127
126
|
Tailing/DMing people and listing users need a user session, not a bot token.
|
|
128
|
-
Switch workspace: slack
|
|
129
|
-
Or import your Slack desktop session: slack
|
|
130
|
-
Import from Slack desktop: slack
|
|
131
|
-
|
|
132
|
-
`+"Get xoxd from browser: DevTools → Application → Cookies → slack.com → d");throw Error(`Slack error on ${u}: ${G}`)}return $}function ju(D,u,F={},E){let C=new URLSearchParams({token:D,...F});return u7(D,u,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:C.toString()},E)}function CD(D,u,F,E){let C=new URLSearchParams(F).toString();return O1(D,`${u}?${C}`,{method:"GET"},E)}function PD(D,u,F){return O1(D,u,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(F)})}async function V1(D){let u=await CD(D,"auth.test",{});return{team:u.team??"",teamId:u.team_id??"",url:u.url??"",user:u.user??"",userId:u.user_id??""}}async function vu(D,u,F=20,E,C){let B={channel:u,limit:String(F)};if(E!==void 0)B.oldest=E;if(C!==void 0)B.cursor=C;return CD(D,"conversations.history",B)}async function Au(D,u,F,E=50){return CD(D,"conversations.replies",{channel:u,ts:F,limit:String(E)})}async function H1(D,u,F){return CD(D,"files.info",{file:u},F)}async function q1(D,u,F,E){return CD(D,"search.messages",{query:u,sort:"timestamp",sort_dir:"desc",count:String(Math.min(Math.max(F,1),100)),page:String(Math.max(E,1))})}async function U1(D,u){return q1(D,u,100,1)}async function M1(D,u,F){let C=1,B=[],$={ok:!0,messages:{}};while(!0){let z=await q1(D,u,100,C),Z=hu(z,["messages","matches"]),Y=Array.isArray(Z)?Z:[];B.push(...Y);let J=Number(hu(z,["messages","paging","pages"])??1);if($=z,Y.length===0||C>=J||B.length>=F)break;C+=1}let G=$;if(!G.messages)G.messages={};return G.messages.matches=B.slice(0,F),G}async function K1(D,u,F,E,C){let B={channel:u,text:F,blocks:[{type:"markdown",text:F}]};if(E!==void 0)B.thread_ts=E;if(C&&E!==void 0)B.reply_broadcast=!0;return(await PD(D,"chat.postMessage",B)).ts??""}async function _1(D,u,F,E,C){let B={channel:u,text:F,post_at:E,blocks:[{type:"markdown",text:F}]};if(C!==void 0)B.thread_ts=C;return(await PD(D,"chat.scheduleMessage",B)).scheduled_message_id??""}async function j1(D,u){let F={};if(u)F.channel=u;return CD(D,"chat.scheduledMessages.list",F)}async function A1(D,u,F){await PD(D,"chat.deleteScheduledMessage",{channel:u,scheduled_message_id:F})}async function L1(D,u,F){return(await CD(D,"chat.getPermalink",{channel:u,message_ts:F})).permalink??""}async function N1(D,u,F,E){return(await PD(D,"chat.update",{channel:u,ts:F,text:E,blocks:[{type:"markdown",text:E}]})).ts??F}async function T1(D,u,F){await PD(D,"chat.delete",{channel:u,ts:F})}async function R1(D,u,F,E){await PD(D,"reactions.add",{channel:u,timestamp:F,name:E})}async function S1(D,u,F,E){await PD(D,"reactions.remove",{channel:u,timestamp:F,name:E})}async function P1(D,u,F=!1){let E=await PD(D,"conversations.create",{name:u,is_private:F});return{id:E.channel?.id??"",name:E.channel?.name??u}}async function w1(D,u,F){await PD(D,"conversations.invite",{channel:u,users:F.join(",")})}async function _F(D){let u=[],F="";while(!0){let E={limit:"200",types:"public_channel,private_channel,im,mpim"};if(F)E.cursor=F;let C=await CD(D,"conversations.list",E);if(u.push(...C.channels??[]),F=C.response_metadata?.next_cursor??"",!F)break}return{channels:u}}async function pu(D,u){let E=(await PD(D,"conversations.open",{users:u})).channel?.id;if(!E)throw Error(`Failed to open DM with user ${u}`);return E}function bD(D){return D.toLowerCase().replace(/[-_\s]/g,"")}async function T0(D,u,F){if(!u.startsWith("@")){if(/^[UW][A-Za-z0-9]{6,}$/.test(u))return u;throw Error(`Expected @user or a user ID, got: ${u}`)}let E=bD(u.slice(1)),C=await CD(D,"auth.test",{},F);if(E==="you"||E==="me"||bD(C.user??"")===E){if(!C.user_id)throw Error("auth.test did not return user_id");return C.user_id}let B="";while(!0){let $={limit:"200"};if(B)$.cursor=B;let G=await CD(D,"users.list",$,F);for(let z of G.members??[]){let Z=bD(z.name??""),Y=bD(z.real_name??""),J=bD(z.profile?.display_name??"");if(Z===E||Y===E||J&&J===E)return z.id??""}if(B=G.response_metadata?.next_cursor??"",!B)break}throw Error(`User not found: ${u}`)}function yu(D){let u=D.match(/(?:app\.slack\.com\/client\/T[A-Za-z0-9]+|[A-Za-z0-9-]+\.slack\.com\/archives)\/([A-Za-z0-9]+)(?:\/p(\d{10})(\d{6}))?/);if(!u)return;let F=u[1],E=u[2]&&u[3]?`${u[2]}.${u[3]}`:void 0,C=D.indexOf("?"),B=C>=0?new URLSearchParams(D.slice(C)):void 0,$=B?.get("thread_ts")??void 0,Z={channel:B?.get("cid")??void 0??F};if(E)Z.ts=E;if($)Z.threadTs=$;return Z}function F7(D){return yu(D)?.channel}async function KD(D,u,F){let E=F7(u);if(E)return E;if(!u.startsWith("@")&&!u.startsWith("#")){if(/^[A-Za-z0-9]{9,}$/.test(u))return u;throw Error(`Target must start with # or @ (or be a Slack URL/ID), got: ${u}`)}let C=u.startsWith("@"),B=u.slice(1),$=bD(B);if(C){let Z=await CD(D,"auth.test",{},F);if($==="you"||$==="me"||bD(Z.user??"")===$){let V=Z.user_id;if(!V)throw Error("auth.test did not return user_id");let M=((await CD(D,"conversations.list",{types:"im",limit:"200"},F)).channels??[]).find((_)=>_.user===V);if(M?.id)return String(M.id);return pu(D,V)}let J="",Q="";while(!0){let V={limit:"200"};if(Q)V.cursor=Q;let W=await CD(D,"users.list",V,F);for(let M of W.members??[]){let _=bD(M.name??""),N=bD(M.real_name??""),k=bD(M.profile?.display_name??"");if(_===$||N===$||k&&k===$){J=M.id??"";break}}if(J)break;if(Q=W.response_metadata?.next_cursor??"",!Q)break}if(!J)throw Error(`User not found: ${u}`);let O="";while(!0){let V={types:"im",limit:"200"};if(O)V.cursor=O;let W=await CD(D,"conversations.list",V,F);for(let M of W.channels??[])if(M.user===J)return String(M.id??"");if(O=W.response_metadata?.next_cursor??"",!O)break}throw Error(`No existing DM with ${u} (${J}). Open it once in Slack first.`)}let G=B.toLowerCase(),z="";while(!0){let Z={limit:"200",types:"public_channel,private_channel",exclude_archived:"true"};if(z)Z.cursor=z;let Y=await CD(D,"conversations.list",Z,F);for(let J of Y.channels??[])if(String(J.name??"").toLowerCase()===G)return String(J.id??"");if(z=Y.response_metadata?.next_cursor??"",!z)break}throw Error(`channel not found: ${u}`)}async function mu(D,u){try{let F=await CD(D,"users.info",{user:u}),E=F.user?.profile?.display_name,C=E&&E.length>0?E:F.user?.real_name??F.user?.name??u,B=F.user?.name??u;return[C,B]}catch{return[u,u]}}async function cu(D,u){try{let F=await CD(D,"users.info",{user:u}),E=F.user?.profile?.display_name;if(E&&E.length>0)return E;return F.user?.real_name??F.user?.name??u}catch{return u}}async function R0(D){let u=[],F="";while(!0){let E={limit:"200"};if(F)E.cursor=F;let C=await CD(D,"users.list",E);if(u.push(...C.members??[]),F=C.response_metadata?.next_cursor??"",!F)break}return{members:u}}async function gu(D,u){return ju(D,"drafts.list",{},u)}async function x1(D,u,F,E){let{randomUUID:C}=await import("node:crypto"),B=JSON.stringify([{type:"rich_text",elements:[{type:"rich_text_section",elements:[{type:"text",text:F}]}]}]);return ju(D,"drafts.create",{client_msg_id:C(),destinations:JSON.stringify([{channel_id:u}]),blocks:B,file_ids:JSON.stringify([]),is_from_composer:"true"},E)}async function I1(D,u,F,E,C){let B=JSON.stringify([{type:"rich_text",elements:[{type:"rich_text_section",elements:[{type:"text",text:E}]}]}]),$=(Date.now()/1000).toFixed(6);return ju(D,"drafts.update",{draft_id:u,client_last_updated_ts:$,destinations:JSON.stringify([{channel_id:F}]),blocks:B,file_ids:JSON.stringify([])},C)}async function b1(D,u,F){return ju(D,"conversations.info",{channel:u},F)}async function f1(D,u){return CD(D,"users.info",{user:u})}async function S0(D,u){return CD(D,"conversations.info",{channel:u})}async function h1(D,u,F){let E=(Date.now()/1000).toFixed(6);return ju(D,"drafts.delete",{draft_id:u,client_last_updated_ts:E},F)}async function P0(D,u){let F=await ju(D,"auth.test",{},u);return{userId:String(F.user_id??""),teamId:String(F.team_id??"")}}async function v1(D,u,F,E={}){let{statSync:C,readFileSync:B}=await import("node:fs"),{basename:$}=await import("node:path"),G=C(F),z=$(F),Z=await CD(D,"files.getUploadURLExternal",{filename:z,length:String(G.size)}),Y=Z.upload_url,J=Z.file_id;if(!Y||!J)throw Error("files.getUploadURLExternal returned no upload_url/file_id");let Q=B(F),O=await fetch(Y,{method:"POST",headers:{"Content-Type":"application/octet-stream"},body:Q});if(!O.ok)throw Error(`Upload PUT failed: ${O.status} ${O.statusText}`);let V={files:[{id:J,title:E.title??z}],channel_id:u};if(E.threadTs)V.thread_ts=E.threadTs;if(E.initialComment)V.initial_comment=E.initialComment;let W=await PD(D,"files.completeUploadExternal",V),M={fileId:J},_=W.files?.[0]?.permalink;if(_)M.permalink=_;return M}function hu(D,u){let F=D;for(let E of u){if(F===void 0||F===null)return;if(typeof E==="number"){if(!Array.isArray(F))return;F=F[E]??void 0}else{if(typeof F!=="object"||Array.isArray(F))return;F=F[E]}}return F}function p1(){return/(?<![A-Za-z0-9._@<-])@([A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*)/g}function jF(D){return D.toLowerCase().replace(/[-_\s]/g,"")}function Vu(D){return D&&typeof D==="object"&&!Array.isArray(D)?D:{}}function y1(D,u){let F=jF(u),E=Vu(D.profile);for(let B of[D.name,D.real_name,E.display_name,E.real_name])if(typeof B==="string"&&B&&jF(B)===F)return!0;let C=typeof E.email==="string"?E.email.toLowerCase():"";return C!==""&&C===u.toLowerCase()}async function x0(D,u,F,E={}){let C=E.warn??((Y)=>process.stderr.write(`${Y}
|
|
127
|
+
Switch workspace: slack auth ls then slack auth use <name>
|
|
128
|
+
Or import your Slack desktop session: slack auth login`);throw Error(`Slack error on ${u}: ${G}`)}return $}async function u7(D,u,F,E){let C={};if(E)C.Cookie=`d=${E}`;let B=await fetch(`${W1()}/${u}`,{...F,headers:{...F.headers??{},Authorization:`Bearer ${D}`,...C}});if(B.status===429){let G=parseInt(B.headers.get("retry-after")??"60",10);throw new fu(isNaN(G)?60:G)}let $=await B.json();if($.ok!==!0){let G=$.error??"unknown";if(G==="ratelimited")throw new fu(60);if((G==="invalid_auth"||G==="not_authed")&&!D.startsWith("xoxc-"))throw Error(`The draft API requires a desktop app session token (xoxc-).
|
|
129
|
+
Import from Slack desktop: slack auth login`);if((G==="invalid_auth"||G==="not_authed")&&!E)throw Error(`drafts.list also requires the xoxd session cookie.
|
|
130
|
+
Attach it with: slack auth chrome (macOS) or slack auth firefox`);throw Error(`Slack error on ${u}: ${G}`)}return $}function ju(D,u,F={},E){let C=new URLSearchParams({token:D,...F});return u7(D,u,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:C.toString()},E)}function CD(D,u,F,E){let C=new URLSearchParams(F).toString();return O1(D,`${u}?${C}`,{method:"GET"},E)}function PD(D,u,F){return O1(D,u,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(F)})}async function V1(D){let u=await CD(D,"auth.test",{});return{team:u.team??"",teamId:u.team_id??"",url:u.url??"",user:u.user??"",userId:u.user_id??""}}async function vu(D,u,F=20,E,C){let B={channel:u,limit:String(F)};if(E!==void 0)B.oldest=E;if(C!==void 0)B.cursor=C;return CD(D,"conversations.history",B)}async function Au(D,u,F,E=50){return CD(D,"conversations.replies",{channel:u,ts:F,limit:String(E)})}async function H1(D,u,F){return CD(D,"files.info",{file:u},F)}async function q1(D,u,F,E){return CD(D,"search.messages",{query:u,sort:"timestamp",sort_dir:"desc",count:String(Math.min(Math.max(F,1),100)),page:String(Math.max(E,1))})}async function U1(D,u){return q1(D,u,100,1)}async function M1(D,u,F){let C=1,B=[],$={ok:!0,messages:{}};while(!0){let z=await q1(D,u,100,C),Z=hu(z,["messages","matches"]),Y=Array.isArray(Z)?Z:[];B.push(...Y);let J=Number(hu(z,["messages","paging","pages"])??1);if($=z,Y.length===0||C>=J||B.length>=F)break;C+=1}let G=$;if(!G.messages)G.messages={};return G.messages.matches=B.slice(0,F),G}async function K1(D,u,F,E,C){let B={channel:u,text:F,blocks:[{type:"markdown",text:F}]};if(E!==void 0)B.thread_ts=E;if(C&&E!==void 0)B.reply_broadcast=!0;return(await PD(D,"chat.postMessage",B)).ts??""}async function _1(D,u,F,E,C){let B={channel:u,text:F,post_at:E,blocks:[{type:"markdown",text:F}]};if(C!==void 0)B.thread_ts=C;return(await PD(D,"chat.scheduleMessage",B)).scheduled_message_id??""}async function j1(D,u){let F={};if(u)F.channel=u;return CD(D,"chat.scheduledMessages.list",F)}async function A1(D,u,F){await PD(D,"chat.deleteScheduledMessage",{channel:u,scheduled_message_id:F})}async function L1(D,u,F){return(await CD(D,"chat.getPermalink",{channel:u,message_ts:F})).permalink??""}async function N1(D,u,F,E){return(await PD(D,"chat.update",{channel:u,ts:F,text:E,blocks:[{type:"markdown",text:E}]})).ts??F}async function T1(D,u,F){await PD(D,"chat.delete",{channel:u,ts:F})}async function R1(D,u,F,E){await PD(D,"reactions.add",{channel:u,timestamp:F,name:E})}async function S1(D,u,F,E){await PD(D,"reactions.remove",{channel:u,timestamp:F,name:E})}async function P1(D,u,F=!1){let E=await PD(D,"conversations.create",{name:u,is_private:F});return{id:E.channel?.id??"",name:E.channel?.name??u}}async function w1(D,u,F){await PD(D,"conversations.invite",{channel:u,users:F.join(",")})}async function _F(D){let u=[],F="";while(!0){let E={limit:"200",types:"public_channel,private_channel,im,mpim"};if(F)E.cursor=F;let C=await CD(D,"conversations.list",E);if(u.push(...C.channels??[]),F=C.response_metadata?.next_cursor??"",!F)break}return{channels:u}}async function pu(D,u){let E=(await PD(D,"conversations.open",{users:u})).channel?.id;if(!E)throw Error(`Failed to open DM with user ${u}`);return E}function bD(D){return D.toLowerCase().replace(/[-_\s]/g,"")}async function T0(D,u,F){if(!u.startsWith("@")){if(/^[UW][A-Za-z0-9]{6,}$/.test(u))return u;throw Error(`Expected @user or a user ID, got: ${u}`)}let E=bD(u.slice(1)),C=await CD(D,"auth.test",{},F);if(E==="you"||E==="me"||bD(C.user??"")===E){if(!C.user_id)throw Error("auth.test did not return user_id");return C.user_id}let B="";while(!0){let $={limit:"200"};if(B)$.cursor=B;let G=await CD(D,"users.list",$,F);for(let z of G.members??[]){let Z=bD(z.name??""),Y=bD(z.real_name??""),J=bD(z.profile?.display_name??"");if(Z===E||Y===E||J&&J===E)return z.id??""}if(B=G.response_metadata?.next_cursor??"",!B)break}throw Error(`User not found: ${u}`)}function yu(D){let u=D.match(/(?:app\.slack\.com\/client\/T[A-Za-z0-9]+|[A-Za-z0-9-]+\.slack\.com\/archives)\/([A-Za-z0-9]+)(?:\/p(\d{10})(\d{6}))?/);if(!u)return;let F=u[1],E=u[2]&&u[3]?`${u[2]}.${u[3]}`:void 0,C=D.indexOf("?"),B=C>=0?new URLSearchParams(D.slice(C)):void 0,$=B?.get("thread_ts")??void 0,Z={channel:B?.get("cid")??void 0??F};if(E)Z.ts=E;if($)Z.threadTs=$;return Z}function F7(D){return yu(D)?.channel}async function KD(D,u,F){let E=F7(u);if(E)return E;if(!u.startsWith("@")&&!u.startsWith("#")){if(/^[A-Za-z0-9]{9,}$/.test(u))return u;throw Error(`Target must start with # or @ (or be a Slack URL/ID), got: ${u}`)}let C=u.startsWith("@"),B=u.slice(1),$=bD(B);if(C){let Z=await CD(D,"auth.test",{},F);if($==="you"||$==="me"||bD(Z.user??"")===$){let V=Z.user_id;if(!V)throw Error("auth.test did not return user_id");let M=((await CD(D,"conversations.list",{types:"im",limit:"200"},F)).channels??[]).find((_)=>_.user===V);if(M?.id)return String(M.id);return pu(D,V)}let J="",Q="";while(!0){let V={limit:"200"};if(Q)V.cursor=Q;let W=await CD(D,"users.list",V,F);for(let M of W.members??[]){let _=bD(M.name??""),N=bD(M.real_name??""),k=bD(M.profile?.display_name??"");if(_===$||N===$||k&&k===$){J=M.id??"";break}}if(J)break;if(Q=W.response_metadata?.next_cursor??"",!Q)break}if(!J)throw Error(`User not found: ${u}`);let O="";while(!0){let V={types:"im",limit:"200"};if(O)V.cursor=O;let W=await CD(D,"conversations.list",V,F);for(let M of W.channels??[])if(M.user===J)return String(M.id??"");if(O=W.response_metadata?.next_cursor??"",!O)break}throw Error(`No existing DM with ${u} (${J}). Open it once in Slack first.`)}let G=B.toLowerCase(),z="";while(!0){let Z={limit:"200",types:"public_channel,private_channel",exclude_archived:"true"};if(z)Z.cursor=z;let Y=await CD(D,"conversations.list",Z,F);for(let J of Y.channels??[])if(String(J.name??"").toLowerCase()===G)return String(J.id??"");if(z=Y.response_metadata?.next_cursor??"",!z)break}throw Error(`channel not found: ${u}`)}async function mu(D,u){try{let F=await CD(D,"users.info",{user:u}),E=F.user?.profile?.display_name,C=E&&E.length>0?E:F.user?.real_name??F.user?.name??u,B=F.user?.name??u;return[C,B]}catch{return[u,u]}}async function cu(D,u){try{let F=await CD(D,"users.info",{user:u}),E=F.user?.profile?.display_name;if(E&&E.length>0)return E;return F.user?.real_name??F.user?.name??u}catch{return u}}async function R0(D){let u=[],F="";while(!0){let E={limit:"200"};if(F)E.cursor=F;let C=await CD(D,"users.list",E);if(u.push(...C.members??[]),F=C.response_metadata?.next_cursor??"",!F)break}return{members:u}}async function gu(D,u){return ju(D,"drafts.list",{},u)}async function x1(D,u,F,E){let{randomUUID:C}=await import("node:crypto"),B=JSON.stringify([{type:"rich_text",elements:[{type:"rich_text_section",elements:[{type:"text",text:F}]}]}]);return ju(D,"drafts.create",{client_msg_id:C(),destinations:JSON.stringify([{channel_id:u}]),blocks:B,file_ids:JSON.stringify([]),is_from_composer:"true"},E)}async function I1(D,u,F,E,C){let B=JSON.stringify([{type:"rich_text",elements:[{type:"rich_text_section",elements:[{type:"text",text:E}]}]}]),$=(Date.now()/1000).toFixed(6);return ju(D,"drafts.update",{draft_id:u,client_last_updated_ts:$,destinations:JSON.stringify([{channel_id:F}]),blocks:B,file_ids:JSON.stringify([])},C)}async function b1(D,u,F){return ju(D,"conversations.info",{channel:u},F)}async function f1(D,u){return CD(D,"users.info",{user:u})}async function S0(D,u){return CD(D,"conversations.info",{channel:u})}async function h1(D,u,F){let E=(Date.now()/1000).toFixed(6);return ju(D,"drafts.delete",{draft_id:u,client_last_updated_ts:E},F)}async function P0(D,u){let F=await ju(D,"auth.test",{},u);return{userId:String(F.user_id??""),teamId:String(F.team_id??"")}}async function v1(D,u,F,E={}){let{statSync:C,readFileSync:B}=await import("node:fs"),{basename:$}=await import("node:path"),G=C(F),z=$(F),Z=await CD(D,"files.getUploadURLExternal",{filename:z,length:String(G.size)}),Y=Z.upload_url,J=Z.file_id;if(!Y||!J)throw Error("files.getUploadURLExternal returned no upload_url/file_id");let Q=B(F),O=await fetch(Y,{method:"POST",headers:{"Content-Type":"application/octet-stream"},body:Q});if(!O.ok)throw Error(`Upload PUT failed: ${O.status} ${O.statusText}`);let V={files:[{id:J,title:E.title??z}],channel_id:u};if(E.threadTs)V.thread_ts=E.threadTs;if(E.initialComment)V.initial_comment=E.initialComment;let W=await PD(D,"files.completeUploadExternal",V),M={fileId:J},_=W.files?.[0]?.permalink;if(_)M.permalink=_;return M}function hu(D,u){let F=D;for(let E of u){if(F===void 0||F===null)return;if(typeof E==="number"){if(!Array.isArray(F))return;F=F[E]??void 0}else{if(typeof F!=="object"||Array.isArray(F))return;F=F[E]}}return F}function p1(){return/(?<![A-Za-z0-9._@<-])@([A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*)/g}function jF(D){return D.toLowerCase().replace(/[-_\s]/g,"")}function Vu(D){return D&&typeof D==="object"&&!Array.isArray(D)?D:{}}function y1(D,u){let F=jF(u),E=Vu(D.profile);for(let B of[D.name,D.real_name,E.display_name,E.real_name])if(typeof B==="string"&&B&&jF(B)===F)return!0;let C=typeof E.email==="string"?E.email.toLowerCase():"";return C!==""&&C===u.toLowerCase()}async function x0(D,u,F,E={}){let C=E.warn??((Y)=>process.stderr.write(`${Y}
|
|
133
131
|
`)),B=new Set;for(let Y of u.matchAll(p1()))B.add(Y[1]);if(B.size===0)return u;let $=new Map,z=((await YF(D)).members??[]).map(Vu),Z=[];for(let Y of B){let J=z.find((O)=>y1(O,Y)),Q=J&&typeof J.id==="string"?J.id:"";if(Q)$.set(Y,Q);else Z.push(Y)}if(Z.length>0&&F){let Y=await W0(D,F),J=[];for(let Q of Y){let O=Vu(await O0(D,Q)),V=Vu(O.user);if(typeof V.id!=="string")V.id=Q;J.push(V)}for(let Q of Z){let O=J.find((W)=>y1(W,Q)),V=O&&typeof O.id==="string"?O.id:"";if(V)$.set(Q,V)}}for(let Y of B)if(!$.has(Y))C(`warn: unresolved mention @${Y} (left as text)`);return u.replace(p1(),(Y,J)=>{let Q=$.get(J);return Q?`<@${Q}>`:Y})}var E7=["さん","さま","様","君","くん","ちゃん","氏","先生","先輩","殿","老师","老師","兄","姐","哥","姐妹"],C7="\\p{Script=Han}\\p{Script=Hiragana}\\p{Script=Katakana}A-Za-z0-9";function m1(D){return/[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}]/u.test(D)}function B7(D){let u=[],F=new RegExp(`([${C7}]{1,12})(${E7.join("|")})`,"gu");for(let E of D.matchAll(F))u.push({ref:E[1],honorific:E[2]});for(let E of D.matchAll(/\b(?:dear|hi|hello|hey)\b[ \t]+([A-Z][A-Za-z'’-]{1,20})/gi))u.push({ref:E[1],honorific:""});for(let E of D.matchAll(/(?:^|\n)[ \t]*([A-Z][A-Za-z'’-]{1,20}),/g))u.push({ref:E[1],honorific:""});return u}function $7(D){let u=Vu(D.profile),F=new Set;for(let E of[D.name,D.real_name,u.display_name,u.real_name]){if(typeof E!=="string"||!E)continue;for(let C of[E,...E.split(/\s+/)]){let B=jF(C);if(B.length>=2)F.add(B)}}return[...F]}function G7(D,u){let F=jF(D);if(F.length<2)return;for(let E of u)if(F===E)return E;if(m1(D)){for(let E of u)if(F.includes(E)||E.includes(F))return E}return}function z7(D){return[Vu(D.profile).display_name,D.real_name,D.name].filter((E)=>typeof E==="string"&&E!=="")[0]??String(D.id??"?")}async function c1(D,u){let F=B7(u);if(F.length===0)return[];let E=new Set;for(let z of u.matchAll(/<@([A-Z0-9]+)(?:\|[^>]*)?>/g))E.add(z[1]);let B=((await YF(D)).members??[]).map(Vu).filter((z)=>z.deleted!==!0&&z.is_bot!==!0&&z.id!=="USLACKBOT").map((z)=>({id:typeof z.id==="string"?z.id:"",forms:$7(z),display:z7(z)})).filter((z)=>z.id!==""),$=[],G=new Set;for(let{ref:z,honorific:Z}of F)for(let Y of B){let J=G7(z,Y.forms);if(!J)continue;if(E.has(Y.id)||G.has(Y.id))break;G.add(Y.id),$.push({surface:m1(z)?`${J}${Z}`:z,display:Y.display,userId:Y.id});break}return $}async function Lu(D,u,F){let E=u,C=[],B=E,$=0;while(!0){let G=B.indexOf("<@U",$);if(G===-1)break;let z=B.slice(G+2),Z=z.search(/[>|]/),Y=Z===-1?z:z.slice(0,Z);if(Y.length>=9)C.push(Y);$=G+1}for(let G of C){if(!F.has(G))F.set(G,await QF(D,G));let z=F.get(G)??G;E=E.replaceAll(`<@${G}>`,`@${z}`),E=E.replace(new RegExp(`<@${G}\\|[^>]*>`,"g"),`@${z}`)}return E}function ku(D){return D.replace(/<!date\^(\d+)\^[^|>]*(?:\|([^>]*))?>/g,(u,F,E)=>{let C=Number(F);if(!Number.isFinite(C))return E??"date";let B=new Date(C*1000);if(Number.isNaN(B.getTime()))return E??"date";let $=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],G=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],z=$[B.getUTCDay()]??"",Z=G[B.getUTCMonth()]??"";return`${z}, ${Z} ${String(B.getUTCDate()).padStart(2,"0")}, ${B.getUTCFullYear()}`})}function g1(D,u=new Date){let F=new Date(D*1000),E=w0(u),C=w0(new Date(u.getTime()-86400000)),B=w0(F);if(B===E)return"Today";if(B===C)return"Yesterday";let $=F.toLocaleDateString("en-US",{weekday:"long"}),G=F.toLocaleDateString("en-US",{month:"short"});return`${$}, ${G} ${String(F.getDate()).padStart(2,"0")}`}function w0(D){return`${D.getFullYear()}-${D.getMonth()}-${D.getDate()}`}function k1(D){let u=new Date(D*1000),F=u.getFullYear(),E=String(u.getMonth()+1).padStart(2,"0"),C=String(u.getDate()).padStart(2,"0"),B=String(u.getHours()).padStart(2,"0"),$=String(u.getMinutes()).padStart(2,"0");return`${F}-${E}-${C} ${B}:${$}`}function du(D){if(!t1(D))return;let u=J7(D,"utf8");for(let F of u.split(`
|
|
134
132
|
`)){let E=F.trim();if(!E||E.startsWith("#"))continue;let C=E.indexOf("=");if(C===-1)continue;let B=E.slice(0,C).trim(),$=E.slice(C+1).trim();if($.startsWith('"')&&$.endsWith('"')||$.startsWith("'")&&$.endsWith("'"))$=$.slice(1,-1);if(!(B in process.env))process.env[B]=$}}function Q7(D){X7(D,{recursive:!0});let u=tD(D,".gitignore");if(!t1(u))o1(u,`*
|
|
135
133
|
`)}function Y7(){du(tD(d1(),".slack-cli",".env.local")),du(tD(d1(),".config/slack-cli",".env")),du(tD(process.cwd(),".slack-cli",".env.local")),du(tD(process.cwd(),".env.local")),du(tD(process.cwd(),".env"))}function W7(D){return Z7("sha256").update(D).digest("hex")}function m(D){return D&&typeof D==="object"&&!Array.isArray(D)?D:{}}function o(D){return Array.isArray(D)?D:[]}function AF(D){return Number(D.ts??0)}async function s1(D,u,F){let E=u.user;if(typeof E==="string"){if(!F.has(E))F.set(E,await cu(D,E));return F.get(E)??E}return typeof u.username==="string"?u.username:"bot"}function I0(D){let u=new Date(D*1000),F=u.getUTCFullYear(),E=String(u.getUTCMonth()+1).padStart(2,"0"),C=String(u.getUTCDate()).padStart(2,"0"),B=String(u.getUTCHours()).padStart(2,"0"),$=String(u.getUTCMinutes()).padStart(2,"0"),G=String(u.getUTCSeconds()).padStart(2,"0");return`${F}-${E}-${C} ${B}:${$}:${G}`}function f0(D){let[u,F="000000"]=D.split("."),E=Number(u),C=new Date(E*1000),B=C.getUTCFullYear(),$=String(C.getUTCMonth()+1).padStart(2,"0"),G=String(C.getUTCDate()).padStart(2,"0"),z=String(C.getUTCHours()).padStart(2,"0"),Z=String(C.getUTCMinutes()).padStart(2,"0"),Y=String(C.getUTCSeconds()).padStart(2,"0"),J=F.padEnd(6,"0").slice(0,6);return`${B}-${$}-${G}T${z}:${Z}:${Y}.${J}`}function h0(D){let u=D.match(/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})\.(\d{6})$/);if(!u)throw Error(`Invalid message timestamp "${D}" \u2014 expected 2026-05-11T06:01:04.000100`);let F=Date.parse(u[1]+"Z");if(isNaN(F))throw Error(`Cannot parse date in "${D}"`);return`${Math.floor(F/1000)}.${u[2]}`}function O7(D){return/^\d{4}-\d{2}-\d{2}T/.test(D)?h0(D):D}async function NF(D,u,F,E){let C=typeof u.ts==="string"?u.ts:`${AF(u)}.000000`,B=f0(C),$="?";if(typeof u.user==="string"){let W=u.user,M="@"+W;if(!F.has(M)){let[,_]=await mu(D,W);F.set(M,_)}$=F.get(M)??W}else if(typeof u.username==="string")$=u.username;let G=typeof u.text==="string"?u.text:"",Z=ku(await Lu(D,G,F)).split(`
|
package/package.json
CHANGED
package/ts/auth.ts
CHANGED
|
@@ -281,7 +281,7 @@ export async function cmdAuthChrome(opts: { workspace?: string } = {}): Promise<
|
|
|
281
281
|
console.error(` Target a user-token workspace instead: ${userProfiles.map((p) => p.name).join(", ")}`);
|
|
282
282
|
console.error(` slack auth chrome -w ${userProfiles[0]!.name}`);
|
|
283
283
|
} else {
|
|
284
|
-
console.error(` Import your Slack desktop user session instead: slack
|
|
284
|
+
console.error(` Import your Slack desktop user session instead: slack auth login`);
|
|
285
285
|
}
|
|
286
286
|
process.exit(1);
|
|
287
287
|
}
|
package/ts/profiles.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// Profiles are stored in ~/.config/slack-cli/profiles.json.
|
|
3
3
|
//
|
|
4
4
|
// Workspace selection uses lockfiles:
|
|
5
|
-
// Local (cwd): .slack-cli/workspace — set with: slack
|
|
6
|
-
// Global (home): ~/.slack-cli/workspace — set with: slack
|
|
5
|
+
// Local (cwd): .slack-cli/workspace — set with: slack auth use <name>
|
|
6
|
+
// Global (home): ~/.slack-cli/workspace — set with: slack auth use -g <name>
|
|
7
7
|
//
|
|
8
8
|
// Token resolution order (no --workspace flag):
|
|
9
9
|
// 1. process.env SLACK_TOKEN or SLACK_BOT_TOKEN
|
|
@@ -262,8 +262,8 @@ export function resolveToken(workspaceFlag?: string): string {
|
|
|
262
262
|
if (names.length > 0) {
|
|
263
263
|
throw new Error(
|
|
264
264
|
`Workspace not selected (${names.join(", ")} available).\n` +
|
|
265
|
-
` Select locally: slack
|
|
266
|
-
` Select globally: slack
|
|
265
|
+
` Select locally: slack auth use <name> (writes .slack-cli/workspace)\n` +
|
|
266
|
+
` Select globally: slack auth use -g <name> (writes ~/.slack-cli/workspace)`,
|
|
267
267
|
);
|
|
268
268
|
}
|
|
269
269
|
|
package/ts/slack.ts
CHANGED
|
@@ -44,7 +44,7 @@ async function call(token: string, method: string, init: RequestInit, cookie?: s
|
|
|
44
44
|
throw new Error(
|
|
45
45
|
`Desktop app token (xoxc-) is not accepted by the public Slack API.\n` +
|
|
46
46
|
`Replace it with an xoxp- user token:\n` +
|
|
47
|
-
` slack
|
|
47
|
+
` slack auth token`,
|
|
48
48
|
);
|
|
49
49
|
}
|
|
50
50
|
// Bot tokens (xoxb-) can't act as a user: they lack user scopes (missing_scope) and can't
|
|
@@ -56,8 +56,8 @@ async function call(token: string, method: string, init: RequestInit, cookie?: s
|
|
|
56
56
|
throw new Error(
|
|
57
57
|
`Slack error on ${method}: ${err} — ${why}.\n` +
|
|
58
58
|
`Tailing/DMing people and listing users need a user session, not a bot token.\n` +
|
|
59
|
-
` Switch workspace: slack
|
|
60
|
-
` Or import your Slack desktop session: slack
|
|
59
|
+
` Switch workspace: slack auth ls then slack auth use <name>\n` +
|
|
60
|
+
` Or import your Slack desktop session: slack auth login`,
|
|
61
61
|
);
|
|
62
62
|
}
|
|
63
63
|
throw new Error(`Slack error on ${method}: ${err}`);
|
|
@@ -90,14 +90,13 @@ async function callSession(token: string, method: string, init: RequestInit, coo
|
|
|
90
90
|
if ((err === "invalid_auth" || err === "not_authed") && !token.startsWith("xoxc-")) {
|
|
91
91
|
throw new Error(
|
|
92
92
|
`The draft API requires a desktop app session token (xoxc-).\n` +
|
|
93
|
-
`Import from Slack desktop: slack
|
|
93
|
+
`Import from Slack desktop: slack auth login`,
|
|
94
94
|
);
|
|
95
95
|
}
|
|
96
96
|
if ((err === "invalid_auth" || err === "not_authed") && !cookie) {
|
|
97
97
|
throw new Error(
|
|
98
98
|
`drafts.list also requires the xoxd session cookie.\n` +
|
|
99
|
-
`
|
|
100
|
-
`Get xoxd from browser: DevTools → Application → Cookies → slack.com → d`,
|
|
99
|
+
`Attach it with: slack auth chrome (macOS) or slack auth firefox`,
|
|
101
100
|
);
|
|
102
101
|
}
|
|
103
102
|
throw new Error(`Slack error on ${method}: ${err}`);
|