slack-term 1.7.0 → 1.14.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 +37 -2
- package/dist/cli.js +132 -31
- package/package.json +12 -5
- package/ts/auth.ts +242 -0
- package/ts/cli.ts +780 -476
- package/ts/profiles.ts +17 -12
- package/ts/rtm.ts +180 -0
- package/ts/slack.ts +116 -8
- package/ts/tail.ts +279 -0
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "slack-term",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
|
-
"url": "https://github.com/snomiao/slack-
|
|
6
|
+
"url": "https://github.com/snomiao/slack-term.git"
|
|
7
|
+
},
|
|
8
|
+
"bugs": {
|
|
9
|
+
"url": "https://github.com/snomiao/slack-term/issues"
|
|
7
10
|
},
|
|
8
11
|
"devDependencies": {
|
|
9
12
|
"@semantic-release/commit-analyzer": "^13",
|
|
@@ -12,13 +15,14 @@
|
|
|
12
15
|
"@semantic-release/release-notes-generator": "^14",
|
|
13
16
|
"@types/bun": "latest",
|
|
14
17
|
"@types/node": "^22",
|
|
18
|
+
"@types/yargs": "^17.0.35",
|
|
15
19
|
"@vitest/coverage-v8": "^4.1.4",
|
|
16
20
|
"semantic-release": "^24",
|
|
17
21
|
"typescript": "^5.6",
|
|
18
22
|
"vitest": "^4.1.4"
|
|
19
23
|
},
|
|
20
24
|
"bin": {
|
|
21
|
-
"slack": "./
|
|
25
|
+
"slack": "./dist/cli.js"
|
|
22
26
|
},
|
|
23
27
|
"description": "Slack CLI — read activity/news/mentions, search, send messages with a confirm-hash safety gate.",
|
|
24
28
|
"engines": {
|
|
@@ -31,7 +35,7 @@
|
|
|
31
35
|
"SKILL.md",
|
|
32
36
|
"LICENSE"
|
|
33
37
|
],
|
|
34
|
-
"homepage": "https://github.com/snomiao/slack-
|
|
38
|
+
"homepage": "https://github.com/snomiao/slack-term",
|
|
35
39
|
"keywords": [
|
|
36
40
|
"slack",
|
|
37
41
|
"cli",
|
|
@@ -58,5 +62,8 @@
|
|
|
58
62
|
"anonymize": "bun run tests/anonymize.ts",
|
|
59
63
|
"prepublishOnly": "bun run typecheck && bun run build"
|
|
60
64
|
},
|
|
61
|
-
"type": "module"
|
|
65
|
+
"type": "module",
|
|
66
|
+
"dependencies": {
|
|
67
|
+
"yargs": "^18.0.0"
|
|
68
|
+
}
|
|
62
69
|
}
|
package/ts/auth.ts
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
// Interactive auth setup: slack auth login / slack login
|
|
2
|
+
import { createInterface, type Interface } from "node:readline/promises";
|
|
3
|
+
import { addProfile, listProfiles } from "./profiles.ts";
|
|
4
|
+
import { authTest } from "./slack.ts";
|
|
5
|
+
import { extractSessions } from "./slack-app.ts";
|
|
6
|
+
|
|
7
|
+
const USER_SCOPES = [
|
|
8
|
+
"search:read",
|
|
9
|
+
"channels:history", "groups:history", "im:history", "mpim:history",
|
|
10
|
+
"channels:read", "groups:read", "im:read", "mpim:read",
|
|
11
|
+
"users:read", "chat:write", "files:write",
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
const BOT_SCOPES = [
|
|
15
|
+
"channels:history", "groups:history", "im:history", "mpim:history",
|
|
16
|
+
"channels:read", "groups:read", "im:read", "mpim:read",
|
|
17
|
+
"users:read", "chat:write", "files:write",
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
function slugify(s: string): string {
|
|
21
|
+
return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function userManifest(): string {
|
|
25
|
+
return JSON.stringify(
|
|
26
|
+
{
|
|
27
|
+
display_information: { name: "slack-term" },
|
|
28
|
+
oauth_config: { scopes: { user: USER_SCOPES } },
|
|
29
|
+
settings: { org_deploy_enabled: false, socket_mode_enabled: false, token_rotation_enabled: false },
|
|
30
|
+
},
|
|
31
|
+
null,
|
|
32
|
+
2,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function botManifest(): string {
|
|
37
|
+
return JSON.stringify(
|
|
38
|
+
{
|
|
39
|
+
display_information: { name: "slack-term" },
|
|
40
|
+
features: { bot_user: { display_name: "slack-term", always_online: false } },
|
|
41
|
+
oauth_config: { scopes: { bot: BOT_SCOPES } },
|
|
42
|
+
settings: { org_deploy_enabled: false, socket_mode_enabled: false, token_rotation_enabled: false },
|
|
43
|
+
},
|
|
44
|
+
null,
|
|
45
|
+
2,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function ask(rl: Interface, q: string): Promise<string> {
|
|
50
|
+
return (await rl.question(q)).trim();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function saveToken(rl: Interface | null, token: string, nameOverride?: string): Promise<string> {
|
|
54
|
+
console.error("Verifying token...");
|
|
55
|
+
const info = await authTest(token);
|
|
56
|
+
const defaultName = slugify(info.team);
|
|
57
|
+
let name: string;
|
|
58
|
+
if (nameOverride) {
|
|
59
|
+
name = nameOverride;
|
|
60
|
+
} else if (rl) {
|
|
61
|
+
const nameInput = await ask(rl, `Workspace name [${defaultName}]: `);
|
|
62
|
+
name = nameInput || defaultName;
|
|
63
|
+
} else {
|
|
64
|
+
name = defaultName;
|
|
65
|
+
}
|
|
66
|
+
addProfile(name, { token, ...info });
|
|
67
|
+
console.log(`Saved workspace "${name}": ${info.team} (${info.user})`);
|
|
68
|
+
if (process.env.SLACK_MCP_XOXP_TOKEN) {
|
|
69
|
+
console.log("");
|
|
70
|
+
console.log("Warning: SLACK_MCP_XOXP_TOKEN is set in your environment - it conflicts with profiles.");
|
|
71
|
+
console.log(" Unset it so your new profile is used:");
|
|
72
|
+
console.log(" unset SLACK_MCP_XOXP_TOKEN");
|
|
73
|
+
console.log(" Also remove it from your shell config (~/.zshrc, ~/.bashrc, etc.)");
|
|
74
|
+
} else {
|
|
75
|
+
console.log(` Run: slack auth use -g ${name}`);
|
|
76
|
+
}
|
|
77
|
+
return name;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Shared logic for importing sessions from the Slack desktop app. */
|
|
81
|
+
export async function importFromDesktop(): Promise<void> {
|
|
82
|
+
console.error("Scanning Slack desktop app...");
|
|
83
|
+
const sessions = await extractSessions();
|
|
84
|
+
if (sessions.length === 0) {
|
|
85
|
+
console.error("No sessions found. Make sure Slack is installed and you have signed in at least once.");
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
for (const s of sessions) {
|
|
89
|
+
const teamLabel = s.teamName ?? s.teamId;
|
|
90
|
+
const name = slugify(teamLabel);
|
|
91
|
+
addProfile(name, {
|
|
92
|
+
token: s.token,
|
|
93
|
+
team: teamLabel,
|
|
94
|
+
teamId: s.teamId,
|
|
95
|
+
url: s.url ?? "",
|
|
96
|
+
user: "",
|
|
97
|
+
...(s.cookie ? { cookie: s.cookie } : {}),
|
|
98
|
+
});
|
|
99
|
+
console.log(`Added workspace "${name}": ${teamLabel}${s.cookie ? " + xoxd cookie" : ""}`);
|
|
100
|
+
}
|
|
101
|
+
console.log("");
|
|
102
|
+
if (sessions.length === 1) {
|
|
103
|
+
const name = slugify(sessions[0]!.teamName ?? sessions[0]!.teamId);
|
|
104
|
+
console.log(`Run: slack auth use -g ${name}`);
|
|
105
|
+
} else {
|
|
106
|
+
console.log("Run: slack auth ls then: slack auth use -g <name>");
|
|
107
|
+
}
|
|
108
|
+
console.log("");
|
|
109
|
+
console.log("Note: desktop app tokens (xoxc-) are internal Slack tokens.");
|
|
110
|
+
console.log("If API calls fail, replace with an xoxp- user token by re-running: slack auth login");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function loginExisting(rl: Interface): Promise<void> {
|
|
114
|
+
console.log("Which token type does your app use?");
|
|
115
|
+
console.log("");
|
|
116
|
+
console.log(" 1) User token (xoxp-) -full access including search [recommended]");
|
|
117
|
+
console.log(" 2) Bot token (xoxb-) -search and news unavailable");
|
|
118
|
+
console.log("");
|
|
119
|
+
const typeChoice = await ask(rl, "Choice [1/2]: ");
|
|
120
|
+
const mode = typeChoice === "2" ? "bot" : "user";
|
|
121
|
+
const expectedPrefix = mode === "user" ? "xoxp-" : "xoxb-";
|
|
122
|
+
const tokenSection = mode === "user" ? "User OAuth Token" : "Bot User OAuth Token";
|
|
123
|
+
|
|
124
|
+
console.log("");
|
|
125
|
+
console.log("Find your token here:");
|
|
126
|
+
console.log(" https://api.slack.com/apps");
|
|
127
|
+
console.log(" -> Select your app -> OAuth & Permissions");
|
|
128
|
+
console.log(` -> Copy the "${tokenSection}" (starts with ${expectedPrefix})`);
|
|
129
|
+
console.log("");
|
|
130
|
+
|
|
131
|
+
const token = await ask(rl, "Paste your token: ");
|
|
132
|
+
if (!token) { console.error("No token provided."); process.exit(1); }
|
|
133
|
+
if (!token.startsWith(expectedPrefix)) {
|
|
134
|
+
console.error(`Expected a ${expectedPrefix} token, got: ${token.slice(0, 10)}...`);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
await saveToken(rl, token);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function loginNewApp(rl: Interface, mode: "user" | "bot"): Promise<void> {
|
|
141
|
+
const manifest = mode === "user" ? userManifest() : botManifest();
|
|
142
|
+
const tokenLabel = mode === "user" ? "User OAuth Token (xoxp-...)" : "Bot User OAuth Token (xoxb-...)";
|
|
143
|
+
const expectedPrefix = mode === "user" ? "xoxp-" : "xoxb-";
|
|
144
|
+
|
|
145
|
+
if (mode === "bot") {
|
|
146
|
+
console.log("Note: bot tokens cannot use search:read (user-only scope).");
|
|
147
|
+
console.log(" The news and search commands will not work with a bot token.");
|
|
148
|
+
console.log("");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
console.log("Step 1 -Create your Slack app:");
|
|
152
|
+
console.log(" Open: https://api.slack.com/apps");
|
|
153
|
+
console.log(' Click "Create New App" -> "From a manifest" -> select your workspace');
|
|
154
|
+
console.log(" Paste this manifest (JSON tab):");
|
|
155
|
+
console.log("");
|
|
156
|
+
console.log("-------------------------------------------------------------");
|
|
157
|
+
console.log(manifest);
|
|
158
|
+
console.log("-------------------------------------------------------------");
|
|
159
|
+
console.log("");
|
|
160
|
+
console.log('Step 2 -Install: "Install App" -> "Install to Workspace" -> Authorize');
|
|
161
|
+
console.log("");
|
|
162
|
+
console.log(`Step 3 -Copy your token:`);
|
|
163
|
+
console.log(` OAuth & Permissions -> ${tokenLabel}`);
|
|
164
|
+
console.log("");
|
|
165
|
+
|
|
166
|
+
const token = await ask(rl, "Paste your token: ");
|
|
167
|
+
if (!token) { console.error("No token provided."); process.exit(1); }
|
|
168
|
+
if (!token.startsWith(expectedPrefix)) {
|
|
169
|
+
console.error(`Expected a ${expectedPrefix} token, got: ${token.slice(0, 10)}...`);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
await saveToken(rl, token);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export async function cmdAuthLogin(opts: { token?: string; name?: string } = {}): Promise<void> {
|
|
176
|
+
// Show existing profiles if any
|
|
177
|
+
const existing = listProfiles();
|
|
178
|
+
if (existing.length > 0) {
|
|
179
|
+
console.log("Currently logged in:");
|
|
180
|
+
for (const { name, profile, current } of existing)
|
|
181
|
+
console.log(` ${current ? "*" : " "} ${name} ${profile.team} (${profile.user || "unknown"})`);
|
|
182
|
+
console.log("");
|
|
183
|
+
console.log("Adding another workspace:");
|
|
184
|
+
console.log("");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Non-interactive: token passed via --token flag or piped via stdin
|
|
188
|
+
if (opts.token) {
|
|
189
|
+
await saveToken(null, opts.token, opts.name);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!process.stdin.isTTY) {
|
|
194
|
+
// Read token from stdin (piped)
|
|
195
|
+
const chunks: Buffer[] = [];
|
|
196
|
+
for await (const chunk of process.stdin) chunks.push(chunk as Buffer);
|
|
197
|
+
const token = Buffer.concat(chunks).toString("utf8").trim();
|
|
198
|
+
if (!token) {
|
|
199
|
+
console.error("No token provided on stdin.");
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
await saveToken(null, token, opts.name);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
console.log("How would you like to authenticate with Slack?");
|
|
207
|
+
console.log("");
|
|
208
|
+
console.log(" 1) Slack desktop app - import session token");
|
|
209
|
+
console.log(" Reads the xoxc- token directly from the installed app.");
|
|
210
|
+
console.log(" Token: all platforms | xoxd cookie: macOS only");
|
|
211
|
+
console.log("");
|
|
212
|
+
console.log(" 2) Connect existing Slack app [recommended if you have one]");
|
|
213
|
+
console.log(" Paste a token from an app you already created.");
|
|
214
|
+
console.log("");
|
|
215
|
+
console.log(" 3) Create new Slack app - user token (xoxp-)");
|
|
216
|
+
console.log(" Guided setup with manifest. Full access including search.");
|
|
217
|
+
console.log("");
|
|
218
|
+
console.log(" 4) Create new Slack app - bot token (xoxb-)");
|
|
219
|
+
console.log(" Bot is invited to channels. Search and news unavailable.");
|
|
220
|
+
console.log("");
|
|
221
|
+
|
|
222
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
223
|
+
try {
|
|
224
|
+
const choice = await ask(rl, "Choice [1/2/3/4]: ");
|
|
225
|
+
console.log("");
|
|
226
|
+
|
|
227
|
+
if (choice === "1") {
|
|
228
|
+
await importFromDesktop();
|
|
229
|
+
} else if (choice === "2") {
|
|
230
|
+
await loginExisting(rl);
|
|
231
|
+
} else if (choice === "3") {
|
|
232
|
+
await loginNewApp(rl, "user");
|
|
233
|
+
} else if (choice === "4") {
|
|
234
|
+
await loginNewApp(rl, "bot");
|
|
235
|
+
} else {
|
|
236
|
+
console.error(`Invalid choice: "${choice}". Enter 1, 2, 3, or 4.`);
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
239
|
+
} finally {
|
|
240
|
+
rl.close();
|
|
241
|
+
}
|
|
242
|
+
}
|