slack-term 1.17.0 → 1.19.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slack-term",
3
- "version": "1.17.0",
3
+ "version": "1.19.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/snomiao/slack-term.git"
package/ts/cli.ts CHANGED
@@ -17,6 +17,8 @@ import {
17
17
  authTest,
18
18
  authTestSession,
19
19
  conversationInfoSession,
20
+ createChannel,
21
+ inviteToChannel,
20
22
  createDraft,
21
23
  deleteDraft,
22
24
  updateDraft,
@@ -1128,6 +1130,56 @@ async function main(): Promise<void> {
1128
1130
  console.log(`private: ${ch.is_private === true}`);
1129
1131
  },
1130
1132
  )
1133
+ .command(
1134
+ ["create <name>", "new <name>"],
1135
+ "Create a channel (confirm-hash safety gate)",
1136
+ (y2) => y2
1137
+ .positional("name", { type: "string", demandOption: true, describe: "Channel name (Slack lowercases it; no spaces)" })
1138
+ .option("private", { type: "boolean", default: false, describe: "Create a private channel" })
1139
+ .option("invite", { type: "string", array: true, default: [], describe: "Users to invite after creating (@handle or Uxxxx, repeatable)" })
1140
+ .option("code", { type: "string", describe: "Safety hash to confirm create" }),
1141
+ async (argv) => {
1142
+ const token = tok(argv as W);
1143
+ // Slack lowercases and strips the leading #; normalize for preview + code.
1144
+ const name = argv.name!.replace(/^#/, "").toLowerCase();
1145
+ const isPrivate = argv.private === true;
1146
+ const invites = (argv.invite as string[]).filter(Boolean);
1147
+ const code = safetyCode(name, String(isPrivate), invites.join(","));
1148
+ if (argv.code !== code) requireCode(argv.code, code, [
1149
+ `--- Creating channel -------------------------`,
1150
+ ` name: ${isPrivate ? "🔒 " : "#"}${name}`,
1151
+ ` private: ${isPrivate}`,
1152
+ ...(invites.length ? [` invite: ${invites.join(", ")}`] : []),
1153
+ `--------------------------------────────────`,
1154
+ ]);
1155
+ let created: { id: string; name: string };
1156
+ try {
1157
+ created = await createChannel(token, name, isPrivate);
1158
+ } catch (e: unknown) {
1159
+ console.error(e instanceof Error ? e.message : String(e));
1160
+ process.exit(1);
1161
+ }
1162
+ console.log(`✓ Created ${isPrivate ? "🔒 " : "#"}${created.name} (${created.id})`);
1163
+ if (invites.length) {
1164
+ const ids: string[] = [];
1165
+ for (const ref of invites) {
1166
+ try {
1167
+ ids.push(ref.startsWith("U") || ref.startsWith("W") ? ref : await resolveUserId(token, ref));
1168
+ } catch (e: unknown) {
1169
+ console.error(` ! could not resolve ${ref}: ${e instanceof Error ? e.message : String(e)}`);
1170
+ }
1171
+ }
1172
+ if (ids.length) {
1173
+ try {
1174
+ await inviteToChannel(token, created.id, ids);
1175
+ console.log(`✓ Invited ${ids.length} member${ids.length === 1 ? "" : "s"}`);
1176
+ } catch (e: unknown) {
1177
+ console.error(` ! invite failed: ${e instanceof Error ? e.message : String(e)}`);
1178
+ }
1179
+ }
1180
+ }
1181
+ },
1182
+ )
1131
1183
  .demandCommand(1, "")
1132
1184
  .showHelpOnFail(true),
1133
1185
  )
@@ -1264,14 +1316,17 @@ async function main(): Promise<void> {
1264
1316
  .option("user-id", { type: "string", describe: "Raw user ID (opens DM)" })
1265
1317
  .option("as-bot", { type: "boolean", default: false, describe: "Send via the bot token (xoxb / SLACK_BOT_TOKEN) so a DM notifies the recipient and can be two-way" })
1266
1318
  .option("broadcast", { type: "boolean", default: false, describe: "Also send to channel: broadcast a threaded reply back to the channel (Slack's \"Also send to #channel\" checkbox). Only effective with a thread target." })
1267
- .option("mentions", { type: "boolean", default: false, describe: "Convert @handle tokens in the message to real <@USERID> mentions (resolves via users.list, then channel members for Slack Connect guests; unresolved handles stay as plain text)" }),
1319
+ .option("mentions", { type: "boolean", default: true, describe: "Convert @handle tokens to real <@USERID> mentions (on by default; resolves via users.list, then channel members for Slack Connect guests). Unresolved handles stay as plain text. The confirm preview shows the converted message before sending. Disable with --no-mentions for literal @text." }),
1268
1320
  async (argv) => {
1269
1321
  const args: SendArgs = { target: argv.target!, message: argv.message! };
1270
1322
  if (argv.code) args.code = argv.code;
1271
1323
  if (argv["channel-id"]) args.channelId = argv["channel-id"];
1272
1324
  if (argv["user-id"]) args.userId = argv["user-id"];
1273
1325
  if (argv.broadcast) args.broadcast = true;
1274
- if (argv.mentions) {
1326
+ // Mentions on by default; `--no-mentions` (yargs negation) sets it false.
1327
+ // A message with no `@token` short-circuits inside encodeMentions, so the
1328
+ // common case pays no extra API call.
1329
+ if (argv.mentions !== false) {
1275
1330
  args.mentions = true;
1276
1331
  // Resolve mentions with the user token (has users:read) even when the
1277
1332
  // message itself is sent via the bot token.
@@ -1394,12 +1449,12 @@ async function main(): Promise<void> {
1394
1449
  .positional("newText", { type: "string", demandOption: true })
1395
1450
  .option("code", { type: "string", describe: "Safety hash to confirm edit" })
1396
1451
  .option("channel-id", { type: "string", describe: "Raw channel ID" })
1397
- .option("mentions", { type: "boolean", default: false, describe: "Convert @handle tokens in the new text to real <@USERID> mentions (unresolved handles stay as plain text)" }),
1452
+ .option("mentions", { type: "boolean", default: true, describe: "Convert @handle tokens in the new text to real <@USERID> mentions (on by default). Unresolved handles stay as plain text. Disable with --no-mentions for literal @text." }),
1398
1453
  async (argv) => {
1399
1454
  const args: EditArgs = { target: argv.target!, newText: argv.newText! };
1400
1455
  if (argv.code) args.code = argv.code;
1401
1456
  if (argv["channel-id"]) args.channelId = argv["channel-id"];
1402
- if (argv.mentions) args.mentions = true;
1457
+ if (argv.mentions !== false) args.mentions = true;
1403
1458
  await cmdEdit(tok(argv as W), args);
1404
1459
  },
1405
1460
  )
package/ts/slack.ts CHANGED
@@ -314,6 +314,31 @@ export async function deleteMessage(
314
314
  await post(token, "chat.delete", { channel, ts });
315
315
  }
316
316
 
317
+ // Create a public (or private) channel via conversations.create. Slack
318
+ // lowercases the name and rejects spaces/most punctuation; the raw API error
319
+ // (e.g. name_taken, invalid_name_specials) surfaces to the caller. Returns the
320
+ // new channel's { id, name }.
321
+ export async function createChannel(
322
+ token: string,
323
+ name: string,
324
+ isPrivate = false,
325
+ ): Promise<{ id: string; name: string }> {
326
+ const resp = (await post(token, "conversations.create", {
327
+ name,
328
+ is_private: isPrivate,
329
+ })) as { channel?: { id?: string; name?: string } };
330
+ return { id: resp.channel?.id ?? "", name: resp.channel?.name ?? name };
331
+ }
332
+
333
+ // Invite users to a channel via conversations.invite (up to ~1000 ids).
334
+ export async function inviteToChannel(
335
+ token: string,
336
+ channel: string,
337
+ userIds: string[],
338
+ ): Promise<void> {
339
+ await post(token, "conversations.invite", { channel, users: userIds.join(",") });
340
+ }
341
+
317
342
  export async function listConversations(token: string): Promise<Json> {
318
343
  const allChannels: Json[] = [];
319
344
  let cursor = "";