slack-term 1.18.0 → 1.19.1

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.18.0",
3
+ "version": "1.19.1",
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
  )
package/ts/format.ts CHANGED
@@ -3,11 +3,13 @@
3
3
  import { listConversationMembers, listUsers, userInfo, userName, type Json } from "./slack.ts";
4
4
 
5
5
  /** Match an `@handle` token at a word boundary, capturing the handle.
6
- * - The negative lookbehind keeps `@` inside emails (`a@b.com`) from matching.
6
+ * - The negative lookbehind keeps `@` inside emails (`a@b.com`) from matching,
7
+ * and the `<` keeps already-encoded mentions (`<@U0123>`) from being picked up
8
+ * as a handle named after the user ID (Slack resolves those natively).
7
9
  * - Each `.` must be followed by more handle chars, so a trailing sentence dot
8
10
  * (`thanks @taku.`) is left out of the capture. */
9
11
  function mentionRe(): RegExp {
10
- return /(?<![A-Za-z0-9._@-])@([A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*)/g;
12
+ return /(?<![A-Za-z0-9._@<-])@([A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*)/g;
11
13
  }
12
14
 
13
15
  /** Lowercase + strip hyphens/underscores/whitespace for loose handle matching. */
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 = "";