agent-slack 0.7.1 → 0.8.5

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/index.js CHANGED
@@ -3927,6 +3927,413 @@ function toThreadListMessage(m) {
3927
3927
  return rest;
3928
3928
  }
3929
3929
 
3930
+ // src/slack/user-cache.ts
3931
+ import { createHash } from "node:crypto";
3932
+ import { join as join13 } from "node:path";
3933
+
3934
+ // src/slack/users.ts
3935
+ async function listUsers(client, options) {
3936
+ const limit = Math.min(Math.max(options?.limit ?? 200, 1), 1000);
3937
+ const includeBots = options?.includeBots ?? false;
3938
+ let next_cursor;
3939
+ const [out, dmMap] = await Promise.all([
3940
+ (async () => {
3941
+ const users = [];
3942
+ let cursor = options?.cursor;
3943
+ while (users.length < limit) {
3944
+ const pageSize = Math.min(200, limit - users.length);
3945
+ const resp = await client.api("users.list", { limit: pageSize, cursor });
3946
+ const members = asArray(resp.members).filter(isRecord);
3947
+ for (const m of members) {
3948
+ const id = getString(m.id);
3949
+ if (!id) {
3950
+ continue;
3951
+ }
3952
+ if (!includeBots && m.is_bot) {
3953
+ continue;
3954
+ }
3955
+ users.push(toCompactUser(m));
3956
+ if (users.length >= limit) {
3957
+ break;
3958
+ }
3959
+ }
3960
+ const meta = isRecord(resp.response_metadata) ? resp.response_metadata : null;
3961
+ const next = meta ? getString(meta.next_cursor) : undefined;
3962
+ if (!next) {
3963
+ break;
3964
+ }
3965
+ cursor = next;
3966
+ next_cursor = next;
3967
+ }
3968
+ return users;
3969
+ })(),
3970
+ fetchDmMap(client)
3971
+ ]);
3972
+ for (const u of out) {
3973
+ const dmId = dmMap.get(u.id);
3974
+ if (dmId) {
3975
+ u.dm_id = dmId;
3976
+ }
3977
+ }
3978
+ return { users: out, next_cursor };
3979
+ }
3980
+ async function getUser(client, input) {
3981
+ const trimmed = input.trim();
3982
+ if (!trimmed) {
3983
+ throw new Error("User is empty");
3984
+ }
3985
+ const userId = await resolveUserId(client, trimmed);
3986
+ if (!userId) {
3987
+ throw new Error(`Could not resolve user: ${input}`);
3988
+ }
3989
+ const resp = await client.api("users.info", { user: userId });
3990
+ const u = isRecord(resp.user) ? resp.user : null;
3991
+ if (!u || !getString(u.id)) {
3992
+ throw new Error("users.info returned no user");
3993
+ }
3994
+ return toCompactUser(u);
3995
+ }
3996
+ async function resolveUserId(client, input) {
3997
+ const trimmed = input.trim();
3998
+ if (/^U[A-Z0-9]{8,}$/.test(trimmed)) {
3999
+ return trimmed;
4000
+ }
4001
+ const looksLikeEmail = /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(trimmed) && !trimmed.startsWith("@");
4002
+ if (looksLikeEmail) {
4003
+ try {
4004
+ const byEmail = await client.api("users.lookupByEmail", { email: trimmed });
4005
+ const user = isRecord(byEmail.user) ? byEmail.user : null;
4006
+ const userId = user ? getString(user.id) : undefined;
4007
+ if (userId) {
4008
+ return userId;
4009
+ }
4010
+ } catch {}
4011
+ }
4012
+ const handle = trimmed.startsWith("@") ? trimmed.slice(1) : trimmed;
4013
+ if (!handle) {
4014
+ return null;
4015
+ }
4016
+ const handleLower = handle.toLowerCase();
4017
+ let cursor;
4018
+ for (;; ) {
4019
+ const resp = await client.api("users.list", { limit: 200, cursor });
4020
+ const members = asArray(resp.members).filter(isRecord);
4021
+ const found = members.find((m) => {
4022
+ if (getString(m.name)?.toLowerCase() === handleLower) {
4023
+ return true;
4024
+ }
4025
+ if (looksLikeEmail) {
4026
+ const profile = isRecord(m.profile) ? m.profile : null;
4027
+ const email = profile ? getString(profile.email) : undefined;
4028
+ return Boolean(email) && email?.toLowerCase() === trimmed.toLowerCase();
4029
+ }
4030
+ return false;
4031
+ });
4032
+ if (found) {
4033
+ const id = getString(found.id);
4034
+ if (id) {
4035
+ return id;
4036
+ }
4037
+ }
4038
+ const meta = isRecord(resp.response_metadata) ? resp.response_metadata : null;
4039
+ const next = meta ? getString(meta.next_cursor) : undefined;
4040
+ if (!next) {
4041
+ break;
4042
+ }
4043
+ cursor = next;
4044
+ }
4045
+ return null;
4046
+ }
4047
+ async function fetchDmMap(client) {
4048
+ const map = new Map;
4049
+ let cursor;
4050
+ for (;; ) {
4051
+ const resp = await client.api("conversations.list", {
4052
+ types: "im",
4053
+ limit: 200,
4054
+ cursor
4055
+ });
4056
+ const channels = asArray(resp.channels).filter(isRecord);
4057
+ for (const ch of channels) {
4058
+ const id = getString(ch.id);
4059
+ const user = getString(ch.user);
4060
+ if (id && user) {
4061
+ map.set(user, id);
4062
+ }
4063
+ }
4064
+ const meta = isRecord(resp.response_metadata) ? resp.response_metadata : null;
4065
+ const next = meta ? getString(meta.next_cursor) : undefined;
4066
+ if (!next) {
4067
+ break;
4068
+ }
4069
+ cursor = next;
4070
+ }
4071
+ return map;
4072
+ }
4073
+ async function getDmChannelForUsers(client, inputs) {
4074
+ if (!inputs || inputs.length === 0) {
4075
+ throw new Error("At least one user is required");
4076
+ }
4077
+ if (inputs.length > 8) {
4078
+ throw new Error("Slack supports a maximum of 8 users in a group DM");
4079
+ }
4080
+ const userIds = [];
4081
+ for (const input of inputs) {
4082
+ const trimmed = input.trim();
4083
+ if (!trimmed) {
4084
+ continue;
4085
+ }
4086
+ const userId = await resolveUserId(client, trimmed);
4087
+ if (!userId) {
4088
+ throw new Error(`Could not resolve user: ${input}`);
4089
+ }
4090
+ userIds.push(userId);
4091
+ }
4092
+ if (userIds.length === 0) {
4093
+ throw new Error("No valid users provided");
4094
+ }
4095
+ const resp = await client.api("conversations.open", { users: userIds.join(",") });
4096
+ const channel = isRecord(resp.channel) ? resp.channel : null;
4097
+ const channelId = channel ? getString(channel.id) : null;
4098
+ if (!channelId) {
4099
+ throw new Error("conversations.open returned no channel");
4100
+ }
4101
+ const channelType = channelId.startsWith("D") ? "dm" : "group_dm";
4102
+ return {
4103
+ user_ids: userIds,
4104
+ dm_channel_id: channelId,
4105
+ channel_type: channelType
4106
+ };
4107
+ }
4108
+ function toCompactUser(u) {
4109
+ const profile = isRecord(u.profile) ? u.profile : {};
4110
+ return {
4111
+ id: getString(u.id) ?? "",
4112
+ name: getString(u.name) ?? undefined,
4113
+ real_name: getString(u.real_name) ?? getString(profile.real_name) ?? undefined,
4114
+ display_name: getString(profile.display_name) ?? undefined,
4115
+ email: getString(profile.email) ?? undefined,
4116
+ title: getString(profile.title) ?? undefined,
4117
+ tz: getString(u.tz) ?? undefined,
4118
+ is_bot: typeof u.is_bot === "boolean" ? u.is_bot : undefined,
4119
+ deleted: typeof u.deleted === "boolean" ? u.deleted : undefined
4120
+ };
4121
+ }
4122
+
4123
+ // src/slack/user-cache.ts
4124
+ var CACHE_VERSION = 1;
4125
+ var USER_TTL_MS = 24 * 60 * 60 * 1000;
4126
+ var USER_ID_PATTERN = /^(U|W)[A-Z0-9]{8,}$/;
4127
+ var USER_MENTION_PATTERN = /<@((U|W)[A-Z0-9]{8,})(?:\|[^>]+)?>/g;
4128
+ async function resolveUsersById(input) {
4129
+ const uniqueIds = dedupeUserIds(input.userIds);
4130
+ if (uniqueIds.length === 0) {
4131
+ return new Map;
4132
+ }
4133
+ const forceRefresh = input.forceRefresh ?? false;
4134
+ const now = Date.now();
4135
+ const workspaceKey = hashWorkspaceUrl(input.workspaceUrl);
4136
+ const isUnknownWorkspace = workspaceKey === "unknown";
4137
+ const cachePath = isUnknownWorkspace ? "" : join13(getAppDir(), `users-cache-${workspaceKey}.json`);
4138
+ const diskCache = cachePath ? await loadCache(cachePath) : { version: CACHE_VERSION, entries: {} };
4139
+ const out = new Map;
4140
+ const missing = [];
4141
+ for (const userId of uniqueIds) {
4142
+ const cached = diskCache.entries[userId];
4143
+ if (!forceRefresh && cached && now - cached.fetched_at < USER_TTL_MS) {
4144
+ out.set(userId, cached.user);
4145
+ continue;
4146
+ }
4147
+ missing.push(userId);
4148
+ }
4149
+ let cacheChanged = false;
4150
+ if (missing.length > 0) {
4151
+ const fetched = [];
4152
+ const concurrency = 5;
4153
+ for (let i = 0;i < missing.length; i += concurrency) {
4154
+ const chunk = missing.slice(i, i + concurrency);
4155
+ const results = await Promise.all(chunk.map(async (userId) => ({ userId, user: await fetchUserById(input.client, userId) })));
4156
+ fetched.push(...results);
4157
+ }
4158
+ for (const item of fetched) {
4159
+ if (!item.user) {
4160
+ continue;
4161
+ }
4162
+ const entry = {
4163
+ fetched_at: now,
4164
+ user: item.user
4165
+ };
4166
+ diskCache.entries[item.userId] = entry;
4167
+ out.set(item.userId, item.user);
4168
+ cacheChanged = true;
4169
+ }
4170
+ }
4171
+ if (cachePath) {
4172
+ const prunedCache = pruneExpiredEntries(diskCache, now);
4173
+ if (Object.keys(diskCache.entries).length !== Object.keys(prunedCache.entries).length) {
4174
+ cacheChanged = true;
4175
+ }
4176
+ if (cacheChanged) {
4177
+ await writeCache(cachePath, prunedCache);
4178
+ }
4179
+ }
4180
+ return out;
4181
+ }
4182
+ function collectReferencedUserIds(messages, options) {
4183
+ const ids = new Set;
4184
+ const includeReactions = options?.includeReactions ?? false;
4185
+ for (const message of messages) {
4186
+ collectUserIdsFromMessage(message, ids, { includeReactions });
4187
+ }
4188
+ return Array.from(ids);
4189
+ }
4190
+ function toReferencedUsers(userIds, usersById) {
4191
+ const out = {};
4192
+ for (const userId of dedupeUserIds(userIds)) {
4193
+ const user = usersById.get(userId);
4194
+ if (!user) {
4195
+ continue;
4196
+ }
4197
+ out[userId] = user;
4198
+ }
4199
+ return Object.keys(out).length > 0 ? out : undefined;
4200
+ }
4201
+ function dedupeUserIds(ids) {
4202
+ const seen = new Set;
4203
+ for (const raw of ids) {
4204
+ const userId = String(raw).trim();
4205
+ if (!USER_ID_PATTERN.test(userId)) {
4206
+ continue;
4207
+ }
4208
+ seen.add(userId);
4209
+ }
4210
+ return Array.from(seen);
4211
+ }
4212
+ function hashWorkspaceUrl(workspaceUrl) {
4213
+ const trimmed = workspaceUrl.trim();
4214
+ if (!trimmed) {
4215
+ return "unknown";
4216
+ }
4217
+ let source = trimmed;
4218
+ try {
4219
+ source = new URL(trimmed).hostname.toLowerCase();
4220
+ } catch {
4221
+ source = trimmed.toLowerCase();
4222
+ }
4223
+ if (!source || source === "unknown") {
4224
+ return "unknown";
4225
+ }
4226
+ return createHash("sha256").update(source).digest("hex").slice(0, 16);
4227
+ }
4228
+ async function loadCache(path) {
4229
+ const file = await readJsonFile(path);
4230
+ if (!file || file.version !== CACHE_VERSION || !isRecord(file.entries)) {
4231
+ return { version: CACHE_VERSION, entries: {} };
4232
+ }
4233
+ const entries = {};
4234
+ for (const [userId, rawEntry] of Object.entries(file.entries)) {
4235
+ if (!USER_ID_PATTERN.test(userId) || !isRecord(rawEntry)) {
4236
+ continue;
4237
+ }
4238
+ const fetchedAt = typeof rawEntry.fetched_at === "number" ? rawEntry.fetched_at : undefined;
4239
+ const user = isRecord(rawEntry.user) ? toCompactUser(rawEntry.user) : null;
4240
+ if (!fetchedAt || !user) {
4241
+ continue;
4242
+ }
4243
+ entries[userId] = { fetched_at: fetchedAt, user };
4244
+ }
4245
+ return {
4246
+ version: CACHE_VERSION,
4247
+ entries
4248
+ };
4249
+ }
4250
+ async function writeCache(path, file) {
4251
+ try {
4252
+ await writeJsonFile(path, file);
4253
+ } catch {}
4254
+ }
4255
+ function pruneExpiredEntries(file, now) {
4256
+ const next = {};
4257
+ for (const [userId, entry] of Object.entries(file.entries)) {
4258
+ if (now - entry.fetched_at >= USER_TTL_MS) {
4259
+ continue;
4260
+ }
4261
+ next[userId] = entry;
4262
+ }
4263
+ return { version: CACHE_VERSION, entries: next };
4264
+ }
4265
+ async function fetchUserById(client, userId) {
4266
+ try {
4267
+ const resp = await client.api("users.info", { user: userId });
4268
+ const user = isRecord(resp.user) ? resp.user : null;
4269
+ if (!user) {
4270
+ return;
4271
+ }
4272
+ return toCompactUser(user);
4273
+ } catch {
4274
+ return;
4275
+ }
4276
+ }
4277
+ function collectUserIdsFromUnknown(value, out) {
4278
+ if (typeof value === "string") {
4279
+ collectMentionUserIds(value, out);
4280
+ return;
4281
+ }
4282
+ if (Array.isArray(value)) {
4283
+ for (const item of value) {
4284
+ collectUserIdsFromUnknown(item, out);
4285
+ }
4286
+ return;
4287
+ }
4288
+ if (!isRecord(value)) {
4289
+ return;
4290
+ }
4291
+ for (const [key, child] of Object.entries(value)) {
4292
+ if ((key === "user" || key === "user_id") && typeof child === "string") {
4293
+ if (USER_ID_PATTERN.test(child)) {
4294
+ out.add(child);
4295
+ }
4296
+ continue;
4297
+ }
4298
+ if (key === "users") {
4299
+ for (const maybeUserId of asArray(child)) {
4300
+ const userId = String(maybeUserId);
4301
+ if (USER_ID_PATTERN.test(userId)) {
4302
+ out.add(userId);
4303
+ }
4304
+ }
4305
+ continue;
4306
+ }
4307
+ collectUserIdsFromUnknown(child, out);
4308
+ }
4309
+ }
4310
+ function collectUserIdsFromMessage(message, out, options) {
4311
+ if (message.user && USER_ID_PATTERN.test(message.user)) {
4312
+ out.add(message.user);
4313
+ }
4314
+ if (typeof message.text === "string") {
4315
+ collectMentionUserIds(message.text, out);
4316
+ }
4317
+ collectUserIdsFromUnknown(message.blocks, out);
4318
+ collectUserIdsFromUnknown(message.attachments, out);
4319
+ if (options.includeReactions) {
4320
+ collectUserIdsFromUnknown(message.reactions, out);
4321
+ }
4322
+ }
4323
+ function collectMentionUserIds(text, out) {
4324
+ USER_MENTION_PATTERN.lastIndex = 0;
4325
+ for (;; ) {
4326
+ const match = USER_MENTION_PATTERN.exec(text);
4327
+ if (!match) {
4328
+ break;
4329
+ }
4330
+ const userId = match[1] ?? "";
4331
+ if (USER_ID_PATTERN.test(userId)) {
4332
+ out.add(userId);
4333
+ }
4334
+ }
4335
+ }
4336
+
3930
4337
  // src/cli/message-read-actions.ts
3931
4338
  async function handleMessageGet(input) {
3932
4339
  const target = parseMsgTarget(input.targetInput);
@@ -3946,8 +4353,25 @@ async function handleMessageGet(input) {
3946
4353
  const thread2 = await getThreadSummary(client2, { channelId: ref2.channel_id, msg: msg2 });
3947
4354
  const downloadedPaths2 = await downloadMessageFiles({ auth: auth2, messages: [msg2] });
3948
4355
  const maxBodyChars2 = Number.parseInt(input.options.maxBodyChars, 10);
3949
- const message2 = toCompactMessage(msg2, { maxBodyChars: maxBodyChars2, includeReactions: includeReactions2, downloadedPaths: downloadedPaths2 });
3950
- return pruneEmpty({ message: message2, thread: thread2 });
4356
+ const referencedUserIds2 = collectReferencedUserIds([msg2], {
4357
+ includeReactions: includeReactions2
4358
+ });
4359
+ const usersById2 = input.options.resolveUsers || input.options.refreshUsers ? await resolveUsersById({
4360
+ client: client2,
4361
+ workspaceUrl: ref2.workspace_url,
4362
+ userIds: referencedUserIds2,
4363
+ forceRefresh: Boolean(input.options.refreshUsers)
4364
+ }) : new Map;
4365
+ const message2 = toCompactMessage(msg2, {
4366
+ maxBodyChars: maxBodyChars2,
4367
+ includeReactions: includeReactions2,
4368
+ downloadedPaths: downloadedPaths2
4369
+ });
4370
+ return pruneEmpty({
4371
+ message: message2,
4372
+ thread: thread2,
4373
+ referenced_users: toReferencedUsers(referencedUserIds2, usersById2)
4374
+ });
3951
4375
  }
3952
4376
  const ts = input.options.ts?.trim();
3953
4377
  if (!ts) {
@@ -3971,8 +4395,25 @@ async function handleMessageGet(input) {
3971
4395
  const thread = await getThreadSummary(client, { channelId, msg });
3972
4396
  const downloadedPaths = await downloadMessageFiles({ auth, messages: [msg] });
3973
4397
  const maxBodyChars = Number.parseInt(input.options.maxBodyChars, 10);
3974
- const message = toCompactMessage(msg, { maxBodyChars, includeReactions, downloadedPaths });
3975
- return pruneEmpty({ message, thread });
4398
+ const referencedUserIds = collectReferencedUserIds([msg], {
4399
+ includeReactions
4400
+ });
4401
+ const usersById = input.options.resolveUsers || input.options.refreshUsers ? await resolveUsersById({
4402
+ client,
4403
+ workspaceUrl: ref.workspace_url,
4404
+ userIds: referencedUserIds,
4405
+ forceRefresh: Boolean(input.options.refreshUsers)
4406
+ }) : new Map;
4407
+ const message = toCompactMessage(msg, {
4408
+ maxBodyChars,
4409
+ includeReactions,
4410
+ downloadedPaths
4411
+ });
4412
+ return pruneEmpty({
4413
+ message,
4414
+ thread,
4415
+ referenced_users: toReferencedUsers(referencedUserIds, usersById)
4416
+ });
3976
4417
  }
3977
4418
  });
3978
4419
  }
@@ -4005,8 +4446,18 @@ async function handleMessageList(input) {
4005
4446
  });
4006
4447
  const downloadedPaths2 = await downloadMessageFiles({ auth: auth2, messages: threadMessages2 });
4007
4448
  const maxBodyChars2 = Number.parseInt(input.options.maxBodyChars, 10);
4449
+ const referencedUserIds2 = collectReferencedUserIds(threadMessages2, {
4450
+ includeReactions: includeReactions2
4451
+ });
4452
+ const usersById2 = input.options.resolveUsers || input.options.refreshUsers ? await resolveUsersById({
4453
+ client: client2,
4454
+ workspaceUrl: ref.workspace_url,
4455
+ userIds: referencedUserIds2,
4456
+ forceRefresh: Boolean(input.options.refreshUsers)
4457
+ }) : new Map;
4008
4458
  return pruneEmpty({
4009
- messages: threadMessages2.map((m) => toCompactMessage(m, { maxBodyChars: maxBodyChars2, includeReactions: includeReactions2, downloadedPaths: downloadedPaths2 })).map(toThreadListMessage)
4459
+ messages: threadMessages2.map((m) => toCompactMessage(m, { maxBodyChars: maxBodyChars2, includeReactions: includeReactions2, downloadedPaths: downloadedPaths2 })).map(toThreadListMessage),
4460
+ referenced_users: toReferencedUsers(referencedUserIds2, usersById2)
4010
4461
  });
4011
4462
  }
4012
4463
  const { client, auth, workspace_url } = await input.ctx.getClientForWorkspace(workspaceUrl);
@@ -4036,9 +4487,19 @@ async function handleMessageList(input) {
4036
4487
  });
4037
4488
  const downloadedPaths2 = await downloadMessageFiles({ auth, messages: channelMessages });
4038
4489
  const maxBodyChars2 = Number.parseInt(input.options.maxBodyChars, 10);
4490
+ const referencedUserIds2 = collectReferencedUserIds(channelMessages, {
4491
+ includeReactions: includeReactions2
4492
+ });
4493
+ const usersById2 = input.options.resolveUsers || input.options.refreshUsers ? await resolveUsersById({
4494
+ client,
4495
+ workspaceUrl: workspace_url ?? workspaceUrl ?? "",
4496
+ userIds: referencedUserIds2,
4497
+ forceRefresh: Boolean(input.options.refreshUsers)
4498
+ }) : new Map;
4039
4499
  return pruneEmpty({
4040
4500
  channel_id: channelId,
4041
- messages: channelMessages.map((m) => toCompactMessage(m, { maxBodyChars: maxBodyChars2, includeReactions: includeReactions2, downloadedPaths: downloadedPaths2 }))
4501
+ messages: channelMessages.map((m) => toCompactMessage(m, { maxBodyChars: maxBodyChars2, includeReactions: includeReactions2, downloadedPaths: downloadedPaths2 })),
4502
+ referenced_users: toReferencedUsers(referencedUserIds2, usersById2)
4042
4503
  });
4043
4504
  }
4044
4505
  if (hasReactionFilters) {
@@ -4063,8 +4524,18 @@ async function handleMessageList(input) {
4063
4524
  });
4064
4525
  const downloadedPaths = await downloadMessageFiles({ auth, messages: threadMessages });
4065
4526
  const maxBodyChars = Number.parseInt(input.options.maxBodyChars, 10);
4527
+ const referencedUserIds = collectReferencedUserIds(threadMessages, {
4528
+ includeReactions
4529
+ });
4530
+ const usersById = input.options.resolveUsers || input.options.refreshUsers ? await resolveUsersById({
4531
+ client,
4532
+ workspaceUrl: workspace_url ?? workspaceUrl ?? "",
4533
+ userIds: referencedUserIds,
4534
+ forceRefresh: Boolean(input.options.refreshUsers)
4535
+ }) : new Map;
4066
4536
  return pruneEmpty({
4067
- messages: threadMessages.map((m) => toCompactMessage(m, { maxBodyChars, includeReactions, downloadedPaths })).map(toThreadListMessage)
4537
+ messages: threadMessages.map((m) => toCompactMessage(m, { maxBodyChars, includeReactions, downloadedPaths })).map(toThreadListMessage),
4538
+ referenced_users: toReferencedUsers(referencedUserIds, usersById)
4068
4539
  });
4069
4540
  }
4070
4541
  });
@@ -6357,7 +6828,7 @@ function collectOptionValue(value, previous = []) {
6357
6828
  }
6358
6829
  function registerMessageCommand(input) {
6359
6830
  const messageCmd = input.program.command("message").description("Read/write Slack messages (token-efficient JSON)");
6360
- messageCmd.command("get", { isDefault: true }).description("Fetch a single Slack message (with thread summary if any)").argument("<target>", "Slack message URL, #channel, or channel ID").option("--workspace <url>", "Workspace selector (full URL or unique substring; needed when using #channel/channel id across multiple workspaces)").option("--ts <ts>", "Message ts (required when using #channel/channel id)").option("--thread-ts <ts>", "Thread root ts hint (useful for thread permalinks)").option("--max-body-chars <n>", "Max content characters to include (default 8000, -1 for unlimited)", "8000").option("--include-reactions", "Include reactions + reacting users").action(async (...args) => {
6831
+ messageCmd.command("get", { isDefault: true }).description("Fetch a single Slack message (with thread summary if any)").argument("<target>", "Slack message URL, #channel, or channel ID").option("--workspace <url>", "Workspace selector (full URL or unique substring; needed when using #channel/channel id across multiple workspaces)").option("--ts <ts>", "Message ts (required when using #channel/channel id)").option("--thread-ts <ts>", "Thread root ts hint (useful for thread permalinks)").option("--max-body-chars <n>", "Max content characters to include (default 8000, -1 for unlimited)", "8000").option("--include-reactions", "Include reactions + reacting users").option("--resolve-users", "Resolve user IDs to user profiles").option("--refresh-users", "Refresh user profile cache before resolving user IDs (implies --resolve-users)").action(async (...args) => {
6361
6832
  const [targetInput, options] = args;
6362
6833
  try {
6363
6834
  const payload = await handleMessageGet({ ctx: input.ctx, targetInput, options });
@@ -6367,7 +6838,7 @@ function registerMessageCommand(input) {
6367
6838
  process.exitCode = 1;
6368
6839
  }
6369
6840
  });
6370
- messageCmd.command("list").description("List recent channel messages, or fetch a full thread").argument("<target>", "Slack message URL, #channel, or channel ID").option("--workspace <url>", "Workspace selector (full URL or unique substring; needed when using #channel/channel id across multiple workspaces)").option("--thread-ts <ts>", "Thread root ts (lists thread replies instead of channel history)").option("--ts <ts>", "Message ts (resolve message to its thread)").option("--limit <n>", "Max messages to return for channel history (default 25, max 200)").option("--oldest <ts>", "Only messages after this ts (channel history mode)").option("--latest <ts>", "Only messages before this ts (channel history mode)").option("--with-reaction <emoji>", "Only include messages with this reaction (repeatable; channel history mode; requires --oldest)", collectOptionValue, []).option("--without-reaction <emoji>", "Only include messages without this reaction (repeatable; channel history mode; requires --oldest)", collectOptionValue, []).option("--max-body-chars <n>", "Max content characters to include (default 8000, -1 for unlimited)", "8000").option("--include-reactions", "Include reactions + reacting users").action(async (...args) => {
6841
+ messageCmd.command("list").description("List recent channel messages, or fetch a full thread").argument("<target>", "Slack message URL, #channel, or channel ID").option("--workspace <url>", "Workspace selector (full URL or unique substring; needed when using #channel/channel id across multiple workspaces)").option("--thread-ts <ts>", "Thread root ts (lists thread replies instead of channel history)").option("--ts <ts>", "Message ts (resolve message to its thread)").option("--limit <n>", "Max messages to return for channel history (default 25, max 200)").option("--oldest <ts>", "Only messages after this ts (channel history mode)").option("--latest <ts>", "Only messages before this ts (channel history mode)").option("--with-reaction <emoji>", "Only include messages with this reaction (repeatable; channel history mode; requires --oldest)", collectOptionValue, []).option("--without-reaction <emoji>", "Only include messages without this reaction (repeatable; channel history mode; requires --oldest)", collectOptionValue, []).option("--max-body-chars <n>", "Max content characters to include (default 8000, -1 for unlimited)", "8000").option("--include-reactions", "Include reactions + reacting users").option("--resolve-users", "Resolve user IDs to user profiles").option("--refresh-users", "Refresh user profile cache before resolving user IDs (implies --resolve-users)").action(async (...args) => {
6371
6842
  const [targetInput, options] = args;
6372
6843
  try {
6373
6844
  const payload = await handleMessageList({ ctx: input.ctx, targetInput, options });
@@ -6559,7 +7030,7 @@ async function channelTokenForSearch(client, channel) {
6559
7030
  } catch {}
6560
7031
  return null;
6561
7032
  }
6562
- async function resolveUserId(client, input) {
7033
+ async function resolveUserId2(client, input) {
6563
7034
  const trimmed = input.trim();
6564
7035
  if (!trimmed) {
6565
7036
  return;
@@ -6747,7 +7218,7 @@ async function searchFilesViaSearchApi(client, input) {
6747
7218
  }
6748
7219
  async function searchFilesInChannelsFallback(client, input) {
6749
7220
  const channelIds = await Promise.all(input.channels.map((c) => resolveChannelId(client, c)));
6750
- const userId = input.user ? await resolveUserId(client, input.user) : undefined;
7221
+ const userId = input.user ? await resolveUserId2(client, input.user) : undefined;
6751
7222
  const queryLower = input.query.trim().toLowerCase();
6752
7223
  const ts_from = input.after ? dateToUnixSeconds(input.after, "start") : undefined;
6753
7224
  const ts_to = input.before ? dateToUnixSeconds(input.before, "end") : undefined;
@@ -6845,7 +7316,7 @@ function passesFileContentTypeFilter(f, contentType) {
6845
7316
  async function searchMessagesViaSearchApi(client, input) {
6846
7317
  const matches = input.rawMatches;
6847
7318
  if (matches.length === 0) {
6848
- return [];
7319
+ return { messages: [] };
6849
7320
  }
6850
7321
  const messageRefs = [];
6851
7322
  for (const m of matches) {
@@ -6869,6 +7340,7 @@ async function searchMessagesViaSearchApi(client, input) {
6869
7340
  }
6870
7341
  const downloadedPaths = {};
6871
7342
  const downloadsDir = input.download ? await ensureDownloadsDir() : null;
7343
+ const resolvedMessages = [];
6872
7344
  const out = [];
6873
7345
  for (const ref of messageRefs) {
6874
7346
  let full = null;
@@ -6907,21 +7379,36 @@ async function searchMessagesViaSearchApi(client, input) {
6907
7379
  if (!passesContentTypeFilter(compact, input.contentType)) {
6908
7380
  continue;
6909
7381
  }
6910
- out.push(stripThreadListFields(compact));
7382
+ resolvedMessages.push(full);
7383
+ out.push(toSearchCompactMessage(compact, ref.permalink));
6911
7384
  if (out.length >= input.limit) {
6912
7385
  break;
6913
7386
  }
6914
7387
  }
6915
- return out;
7388
+ const referencedUserIds = collectReferencedUserIds(resolvedMessages, {
7389
+ includeReactions: false
7390
+ });
7391
+ const shouldResolveUsers = input.resolveUsers || input.refreshUsers;
7392
+ const usersById = shouldResolveUsers ? await resolveUsersById({
7393
+ client,
7394
+ workspaceUrl: input.workspace_url ?? "",
7395
+ userIds: referencedUserIds,
7396
+ forceRefresh: Boolean(input.refreshUsers)
7397
+ }) : new Map;
7398
+ return {
7399
+ messages: out,
7400
+ referenced_users: toReferencedUsers(referencedUserIds, usersById)
7401
+ };
6916
7402
  }
6917
7403
  async function searchMessagesInChannelsFallback(client, input) {
6918
7404
  const channelIds = await Promise.all(input.channels.map((c) => resolveChannelId(client, c)));
6919
7405
  const queryLower = input.query.trim().toLowerCase();
6920
- const userId = input.user ? await resolveUserId(client, input.user) : undefined;
7406
+ const userId = input.user ? await resolveUserId2(client, input.user) : undefined;
6921
7407
  const afterSec = input.after ? dateToUnixSeconds(input.after, "start") : null;
6922
7408
  const beforeSec = input.before ? dateToUnixSeconds(input.before, "end") : null;
6923
7409
  const downloadsDir = input.download ? await ensureDownloadsDir() : null;
6924
7410
  const downloadedPaths = {};
7411
+ const matchedSummaries = [];
6925
7412
  const results = [];
6926
7413
  for (const channelId of channelIds) {
6927
7414
  let cursorLatest;
@@ -6969,9 +7456,22 @@ async function searchMessagesInChannelsFallback(client, input) {
6969
7456
  if (!passesContentTypeFilter(compact, input.contentType)) {
6970
7457
  continue;
6971
7458
  }
6972
- results.push(stripThreadListFields(compact));
7459
+ matchedSummaries.push(summary);
7460
+ results.push(toSearchCompactMessage(compact));
6973
7461
  if (results.length >= input.limit) {
6974
- return results;
7462
+ const referencedUserIds2 = collectReferencedUserIds(matchedSummaries, {
7463
+ includeReactions: false
7464
+ });
7465
+ const usersById2 = await resolveUsersById({
7466
+ client,
7467
+ workspaceUrl: input.workspace_url ?? "",
7468
+ userIds: referencedUserIds2,
7469
+ forceRefresh: Boolean(input.refreshUsers)
7470
+ });
7471
+ return {
7472
+ messages: results,
7473
+ referenced_users: toReferencedUsers(referencedUserIds2, usersById2)
7474
+ };
6975
7475
  }
6976
7476
  }
6977
7477
  if (!cursorLatest) {
@@ -6984,7 +7484,20 @@ async function searchMessagesInChannelsFallback(client, input) {
6984
7484
  }
6985
7485
  }
6986
7486
  }
6987
- return results;
7487
+ const referencedUserIds = collectReferencedUserIds(matchedSummaries, {
7488
+ includeReactions: false
7489
+ });
7490
+ const shouldResolveUsers = input.resolveUsers || input.refreshUsers;
7491
+ const usersById = shouldResolveUsers ? await resolveUsersById({
7492
+ client,
7493
+ workspaceUrl: input.workspace_url ?? "",
7494
+ userIds: referencedUserIds,
7495
+ forceRefresh: Boolean(input.refreshUsers)
7496
+ }) : new Map;
7497
+ return {
7498
+ messages: results,
7499
+ referenced_users: toReferencedUsers(referencedUserIds, usersById)
7500
+ };
6988
7501
  }
6989
7502
  function passesContentTypeFilter(m, contentType) {
6990
7503
  if (contentType === "any") {
@@ -7008,9 +7521,9 @@ function passesContentTypeFilter(m, contentType) {
7008
7521
  }
7009
7522
  return true;
7010
7523
  }
7011
- function stripThreadListFields(m) {
7012
- const { channel_id: _channelId, thread_ts: _threadTs, ...rest } = m;
7013
- return rest;
7524
+ function toSearchCompactMessage(m, permalink) {
7525
+ const { thread_ts: _threadTs, ...rest } = m;
7526
+ return permalink ? { ...rest, permalink } : rest;
7014
7527
  }
7015
7528
  async function downloadFilesForMessage(input) {
7016
7529
  for (const f of input.message.files ?? []) {
@@ -7102,8 +7615,9 @@ async function searchSlack(input) {
7102
7615
  const out = {};
7103
7616
  if (input.options.kind === "messages" || input.options.kind === "all") {
7104
7617
  if (input.options.channels?.length) {
7105
- out.messages = await searchMessagesInChannelsFallback(input.client, {
7618
+ const messageResult = await searchMessagesInChannelsFallback(input.client, {
7106
7619
  auth: input.auth,
7620
+ workspace_url: input.options.workspace_url,
7107
7621
  query: input.options.query,
7108
7622
  channels: input.options.channels,
7109
7623
  user: input.options.user,
@@ -7112,11 +7626,15 @@ async function searchSlack(input) {
7112
7626
  limit,
7113
7627
  maxContentChars,
7114
7628
  contentType,
7115
- download
7629
+ download,
7630
+ resolveUsers: input.options.resolve_users,
7631
+ refreshUsers: input.options.refresh_users
7116
7632
  });
7633
+ out.messages = messageResult.messages;
7634
+ out.referenced_users = messageResult.referenced_users;
7117
7635
  } else {
7118
7636
  const rawMatches = await searchMessagesRaw(input.client, { query: slackQuery, limit });
7119
- out.messages = await searchMessagesViaSearchApi(input.client, {
7637
+ const messageResult = await searchMessagesViaSearchApi(input.client, {
7120
7638
  auth: input.auth,
7121
7639
  workspace_url: input.options.workspace_url,
7122
7640
  slack_query: slackQuery,
@@ -7124,8 +7642,12 @@ async function searchSlack(input) {
7124
7642
  maxContentChars,
7125
7643
  contentType,
7126
7644
  download,
7127
- rawMatches
7645
+ rawMatches,
7646
+ resolveUsers: input.options.resolve_users,
7647
+ refreshUsers: input.options.refresh_users
7128
7648
  });
7649
+ out.messages = messageResult.messages;
7650
+ out.referenced_users = messageResult.referenced_users;
7129
7651
  }
7130
7652
  }
7131
7653
  if (input.options.kind === "files" || input.options.kind === "all") {
@@ -7156,7 +7678,7 @@ async function searchSlack(input) {
7156
7678
 
7157
7679
  // src/cli/search-command.ts
7158
7680
  function addSearchOptions(cmd) {
7159
- return cmd.option("--workspace <url>", "Workspace selector (full URL or unique substring; needed when searching across multiple workspaces)").option("--channel <channel...>", "Channel filter (#name, name, or id). Repeatable.").option("--user <user>", "User filter (@name, name, or user id U...)").option("--after <date>", "Only results after YYYY-MM-DD").option("--before <date>", "Only results before YYYY-MM-DD").option("--content-type <type>", "Filter content type: any|text|image|snippet|file (default any)").option("--limit <n>", "Max results (default 20)", "20").option("--max-content-chars <n>", "Max message content characters (default 4000, -1 for unlimited)", "4000");
7681
+ return cmd.option("--workspace <url>", "Workspace selector (full URL or unique substring; needed when searching across multiple workspaces)").option("--channel <channel...>", "Channel filter (#name, name, or id). Repeatable.").option("--user <user>", "User filter (@name, name, or user id U...)").option("--after <date>", "Only results after YYYY-MM-DD").option("--before <date>", "Only results before YYYY-MM-DD").option("--content-type <type>", "Filter content type: any|text|image|snippet|file (default any)").option("--limit <n>", "Max results (default 20)", "20").option("--max-content-chars <n>", "Max message content characters (default 4000, -1 for unlimited)", "4000").option("--resolve-users", "Resolve user IDs to user profiles").option("--refresh-users", "Refresh user profile cache before resolving referenced users (implies --resolve-users)");
7160
7682
  }
7161
7683
  async function runSearch(input) {
7162
7684
  const workspaceUrl = input.ctx.effectiveWorkspaceUrl(input.options.workspace);
@@ -7183,7 +7705,9 @@ async function runSearch(input) {
7183
7705
  content_type: contentType,
7184
7706
  limit,
7185
7707
  max_content_chars: maxContentChars,
7186
- download: true
7708
+ download: true,
7709
+ resolve_users: Boolean(input.options.resolveUsers || input.options.refreshUsers),
7710
+ refresh_users: Boolean(input.options.refreshUsers)
7187
7711
  }
7188
7712
  });
7189
7713
  }
@@ -7227,13 +7751,13 @@ async function fetchLaterItems(client, options) {
7227
7751
  const pageRawItems = asArray2(resp.saved_items).filter(isRecord5);
7228
7752
  allRawItems.push(...pageRawItems);
7229
7753
  const meta = isRecord5(resp.response_metadata) ? resp.response_metadata : null;
7230
- nextCursor = meta ? getString4(meta.next_cursor) : undefined;
7754
+ nextCursor = meta ? getString5(meta.next_cursor) : undefined;
7231
7755
  if (countsOnly) {
7232
7756
  break;
7233
7757
  }
7234
- let filtered2 = allRawItems.filter((item) => getString4(item.item_type) === "message");
7758
+ let filtered2 = allRawItems.filter((item) => getString5(item.item_type) === "message");
7235
7759
  if (stateFilter !== "all") {
7236
- filtered2 = filtered2.filter((item) => getString4(item.state) === stateFilter);
7760
+ filtered2 = filtered2.filter((item) => getString5(item.state) === stateFilter);
7237
7761
  }
7238
7762
  if (filtered2.length >= limit || !nextCursor) {
7239
7763
  break;
@@ -7253,15 +7777,15 @@ async function fetchLaterItems(client, options) {
7253
7777
  if (countsOnly) {
7254
7778
  return result;
7255
7779
  }
7256
- let filtered = allRawItems.filter((item) => getString4(item.item_type) === "message");
7780
+ let filtered = allRawItems.filter((item) => getString5(item.item_type) === "message");
7257
7781
  if (stateFilter !== "all") {
7258
- filtered = filtered.filter((item) => getString4(item.state) === stateFilter);
7782
+ filtered = filtered.filter((item) => getString5(item.state) === stateFilter);
7259
7783
  }
7260
7784
  filtered = filtered.slice(0, limit);
7261
7785
  result.items = await Promise.all(filtered.map(async (item) => {
7262
- const channelId = getString4(item.item_id) ?? "";
7263
- const ts = getString4(item.ts) ?? "";
7264
- const state = getString4(item.state) ?? "in_progress";
7786
+ const channelId = getString5(item.item_id) ?? "";
7787
+ const ts = getString5(item.ts) ?? "";
7788
+ const state = getString5(item.state) ?? "in_progress";
7265
7789
  const dateSaved = getNumber3(item.date_created) ?? 0;
7266
7790
  const dateCompleted = getNumber3(item.date_completed);
7267
7791
  let channelName;
@@ -7272,15 +7796,15 @@ async function fetchLaterItems(client, options) {
7272
7796
  });
7273
7797
  const ch = isRecord5(info.channel) ? info.channel : null;
7274
7798
  if (ch) {
7275
- channelName = getString4(ch.name) ?? getString4(ch.name_normalized) ?? undefined;
7799
+ channelName = getString5(ch.name) ?? getString5(ch.name_normalized) ?? undefined;
7276
7800
  if (ch.is_im && !channelName) {
7277
- const userId = getString4(ch.user);
7801
+ const userId = getString5(ch.user);
7278
7802
  if (userId) {
7279
7803
  try {
7280
7804
  const userInfo = await client.api("users.info", { user: userId });
7281
7805
  const u = isRecord5(userInfo.user) ? userInfo.user : null;
7282
7806
  const profile = u && isRecord5(u.profile) ? u.profile : null;
7283
- channelName = getString4(profile?.display_name) || getString4(u?.real_name) || getString4(u?.name) || undefined;
7807
+ channelName = getString5(profile?.display_name) || getString5(u?.real_name) || getString5(u?.name) || undefined;
7284
7808
  } catch {}
7285
7809
  }
7286
7810
  }
@@ -7295,18 +7819,18 @@ async function fetchLaterItems(client, options) {
7295
7819
  limit: 1
7296
7820
  });
7297
7821
  const msgs = asArray2(history.messages).filter(isRecord5);
7298
- const msg = msgs.find((m) => getString4(m.ts) === ts);
7822
+ const msg = msgs.find((m) => getString5(m.ts) === ts);
7299
7823
  if (msg) {
7300
7824
  const rendered = renderSlackMessageContent(msg);
7301
7825
  const content = maxBodyChars >= 0 && rendered.length > maxBodyChars ? `${rendered.slice(0, maxBodyChars)}
7302
7826
  …` : rendered;
7303
7827
  message = {
7304
- author: getString4(msg.user) || getString4(msg.bot_id) ? {
7305
- user_id: getString4(msg.user) ?? undefined,
7306
- bot_id: getString4(msg.bot_id) ?? undefined
7828
+ author: getString5(msg.user) || getString5(msg.bot_id) ? {
7829
+ user_id: getString5(msg.user) ?? undefined,
7830
+ bot_id: getString5(msg.bot_id) ?? undefined
7307
7831
  } : undefined,
7308
7832
  content: content || undefined,
7309
- thread_ts: getString4(msg.thread_ts) ?? undefined,
7833
+ thread_ts: getString5(msg.thread_ts) ?? undefined,
7310
7834
  reply_count: getNumber3(msg.reply_count) ?? undefined
7311
7835
  };
7312
7836
  }
@@ -7404,7 +7928,7 @@ function isRecord5(value) {
7404
7928
  function asArray2(value) {
7405
7929
  return Array.isArray(value) ? value : [];
7406
7930
  }
7407
- function getString4(value) {
7931
+ function getString5(value) {
7408
7932
  return typeof value === "string" ? value : undefined;
7409
7933
  }
7410
7934
  function getNumber3(value) {
@@ -7622,22 +8146,22 @@ async function fetchUnreads(client, options) {
7622
8146
  });
7623
8147
  const ch = isRecord6(info.channel) ? info.channel : null;
7624
8148
  if (ch) {
7625
- name = getString5(ch.name) ?? getString5(ch.name_normalized) ?? undefined;
8149
+ name = getString6(ch.name) ?? getString6(ch.name_normalized) ?? undefined;
7626
8150
  if (ch.is_im) {
7627
8151
  type = "dm";
7628
- const userId = getString5(ch.user);
8152
+ const userId = getString6(ch.user);
7629
8153
  if (userId && !name) {
7630
8154
  try {
7631
8155
  const userInfo = await client.api("users.info", { user: userId });
7632
8156
  const u = isRecord6(userInfo.user) ? userInfo.user : null;
7633
8157
  const profile = u && isRecord6(u.profile) ? u.profile : null;
7634
- name = getString5(profile?.display_name) || getString5(u?.real_name) || getString5(u?.name) || undefined;
8158
+ name = getString6(profile?.display_name) || getString6(u?.real_name) || getString6(u?.name) || undefined;
7635
8159
  } catch {}
7636
8160
  }
7637
8161
  } else if (ch.is_mpim) {
7638
8162
  type = "mpim";
7639
8163
  } else if (ch.is_group || ch.is_private) {
7640
- type = "group";
8164
+ type = "channel";
7641
8165
  } else {
7642
8166
  type = "channel";
7643
8167
  }
@@ -7659,7 +8183,7 @@ async function fetchUnreads(client, options) {
7659
8183
  let msgs = asArray3(history.messages).filter(isRecord6);
7660
8184
  if (skipSystem) {
7661
8185
  msgs = msgs.filter((m) => {
7662
- const subtype = getString5(m.subtype);
8186
+ const subtype = getString6(m.subtype);
7663
8187
  if (!subtype) {
7664
8188
  return true;
7665
8189
  }
@@ -7693,13 +8217,13 @@ async function fetchUnreads(client, options) {
7693
8217
  const content = maxBodyChars >= 0 && rendered.length > maxBodyChars ? `${rendered.slice(0, maxBodyChars)}
7694
8218
  ...` : rendered;
7695
8219
  return {
7696
- ts: getString5(m.ts) ?? "",
7697
- author: getString5(m.user) || getString5(m.bot_id) ? {
7698
- user_id: getString5(m.user) ?? undefined,
7699
- bot_id: getString5(m.bot_id) ?? undefined
8220
+ ts: getString6(m.ts) ?? "",
8221
+ author: getString6(m.user) || getString6(m.bot_id) ? {
8222
+ user_id: getString6(m.user) ?? undefined,
8223
+ bot_id: getString6(m.bot_id) ?? undefined
7700
8224
  } : undefined,
7701
8225
  content: content || undefined,
7702
- thread_ts: getString5(m.thread_ts) ?? undefined,
8226
+ thread_ts: getString6(m.thread_ts) ?? undefined,
7703
8227
  reply_count: getNumber4(m.reply_count) ?? undefined
7704
8228
  };
7705
8229
  });
@@ -7737,7 +8261,7 @@ function isRecord6(value) {
7737
8261
  function asArray3(value) {
7738
8262
  return Array.isArray(value) ? value : [];
7739
8263
  }
7740
- function getString5(value) {
8264
+ function getString6(value) {
7741
8265
  return typeof value === "string" ? value : undefined;
7742
8266
  }
7743
8267
  function getNumber4(value) {
@@ -7771,14 +8295,14 @@ function registerUnreadsCommand(input) {
7771
8295
 
7772
8296
  // src/lib/update.ts
7773
8297
  import { execSync as execSync2 } from "node:child_process";
7774
- import { createHash } from "node:crypto";
8298
+ import { createHash as createHash2 } from "node:crypto";
7775
8299
  import { chmod, copyFile as copyFile2, mkdir as mkdir5, readFile as readFile7, rename, rm as rm3, writeFile as writeFile4 } from "node:fs/promises";
7776
8300
  import { tmpdir as tmpdir5 } from "node:os";
7777
- import { basename as basename3, join as join13 } from "node:path";
8301
+ import { basename as basename3, join as join14 } from "node:path";
7778
8302
  var REPO = "stablyai/agent-slack";
7779
8303
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
7780
8304
  function getCachePath() {
7781
- return join13(getAppDir(), "update-check.json");
8305
+ return join14(getAppDir(), "update-check.json");
7782
8306
  }
7783
8307
  function compareSemver(a, b) {
7784
8308
  const pa = a.replace(/^v/, "").split(".").map(Number);
@@ -7874,16 +8398,16 @@ function detectPlatformAsset() {
7874
8398
  }
7875
8399
  async function sha256(filePath) {
7876
8400
  const data = await readFile7(filePath);
7877
- return createHash("sha256").update(data).digest("hex");
8401
+ return createHash2("sha256").update(data).digest("hex");
7878
8402
  }
7879
8403
  async function performUpdate(latest) {
7880
8404
  const asset = detectPlatformAsset();
7881
8405
  const tag = `v${latest}`;
7882
8406
  const baseUrl = `https://github.com/${REPO}/releases/download/${tag}`;
7883
- const tmp = join13(tmpdir5(), `agent-slack-update-${Date.now()}`);
8407
+ const tmp = join14(tmpdir5(), `agent-slack-update-${Date.now()}`);
7884
8408
  await mkdir5(tmp, { recursive: true });
7885
- const binTmp = join13(tmp, asset);
7886
- const sumsTmp = join13(tmp, "checksums-sha256.txt");
8409
+ const binTmp = join14(tmp, asset);
8410
+ const sumsTmp = join14(tmp, "checksums-sha256.txt");
7887
8411
  try {
7888
8412
  const [binResp, sumsResp] = await Promise.all([
7889
8413
  fetch(`${baseUrl}/${asset}`, { signal: AbortSignal.timeout(120000) }),
@@ -7907,6 +8431,16 @@ async function performUpdate(latest) {
7907
8431
  if (actual !== expected) {
7908
8432
  return { success: false, message: `Checksum mismatch: expected ${expected}, got ${actual}` };
7909
8433
  }
8434
+ if (process.platform === "darwin") {
8435
+ try {
8436
+ execSync2(`codesign --remove-signature ${JSON.stringify(binTmp)}`, {
8437
+ stdio: "ignore"
8438
+ });
8439
+ execSync2(`codesign --sign - ${JSON.stringify(binTmp)}`, {
8440
+ stdio: "ignore"
8441
+ });
8442
+ } catch {}
8443
+ }
7910
8444
  const currentBin = process.execPath;
7911
8445
  const backupPath = `${currentBin}.bak`;
7912
8446
  await rename(currentBin, backupPath);
@@ -7994,194 +8528,6 @@ function registerUpdateCommand(input) {
7994
8528
  });
7995
8529
  }
7996
8530
 
7997
- // src/slack/users.ts
7998
- async function listUsers(client, options) {
7999
- const limit = Math.min(Math.max(options?.limit ?? 200, 1), 1000);
8000
- const includeBots = options?.includeBots ?? false;
8001
- let next_cursor;
8002
- const [out, dmMap] = await Promise.all([
8003
- (async () => {
8004
- const users = [];
8005
- let cursor = options?.cursor;
8006
- while (users.length < limit) {
8007
- const pageSize = Math.min(200, limit - users.length);
8008
- const resp = await client.api("users.list", { limit: pageSize, cursor });
8009
- const members = asArray(resp.members).filter(isRecord);
8010
- for (const m of members) {
8011
- const id = getString(m.id);
8012
- if (!id) {
8013
- continue;
8014
- }
8015
- if (!includeBots && m.is_bot) {
8016
- continue;
8017
- }
8018
- users.push(toCompactUser(m));
8019
- if (users.length >= limit) {
8020
- break;
8021
- }
8022
- }
8023
- const meta = isRecord(resp.response_metadata) ? resp.response_metadata : null;
8024
- const next = meta ? getString(meta.next_cursor) : undefined;
8025
- if (!next) {
8026
- break;
8027
- }
8028
- cursor = next;
8029
- next_cursor = next;
8030
- }
8031
- return users;
8032
- })(),
8033
- fetchDmMap(client)
8034
- ]);
8035
- for (const u of out) {
8036
- const dmId = dmMap.get(u.id);
8037
- if (dmId) {
8038
- u.dm_id = dmId;
8039
- }
8040
- }
8041
- return { users: out, next_cursor };
8042
- }
8043
- async function getUser(client, input) {
8044
- const trimmed = input.trim();
8045
- if (!trimmed) {
8046
- throw new Error("User is empty");
8047
- }
8048
- const userId = await resolveUserId2(client, trimmed);
8049
- if (!userId) {
8050
- throw new Error(`Could not resolve user: ${input}`);
8051
- }
8052
- const resp = await client.api("users.info", { user: userId });
8053
- const u = isRecord(resp.user) ? resp.user : null;
8054
- if (!u || !getString(u.id)) {
8055
- throw new Error("users.info returned no user");
8056
- }
8057
- return toCompactUser(u);
8058
- }
8059
- async function resolveUserId2(client, input) {
8060
- const trimmed = input.trim();
8061
- if (/^U[A-Z0-9]{8,}$/.test(trimmed)) {
8062
- return trimmed;
8063
- }
8064
- const looksLikeEmail = /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(trimmed) && !trimmed.startsWith("@");
8065
- if (looksLikeEmail) {
8066
- try {
8067
- const byEmail = await client.api("users.lookupByEmail", { email: trimmed });
8068
- const user = isRecord(byEmail.user) ? byEmail.user : null;
8069
- const userId = user ? getString(user.id) : undefined;
8070
- if (userId) {
8071
- return userId;
8072
- }
8073
- } catch {}
8074
- }
8075
- const handle = trimmed.startsWith("@") ? trimmed.slice(1) : trimmed;
8076
- if (!handle) {
8077
- return null;
8078
- }
8079
- let cursor;
8080
- for (;; ) {
8081
- const resp = await client.api("users.list", { limit: 200, cursor });
8082
- const members = asArray(resp.members).filter(isRecord);
8083
- const found = members.find((m) => {
8084
- if (getString(m.name) === handle) {
8085
- return true;
8086
- }
8087
- if (looksLikeEmail) {
8088
- const profile = isRecord(m.profile) ? m.profile : null;
8089
- const email = profile ? getString(profile.email) : undefined;
8090
- return Boolean(email) && email?.toLowerCase() === trimmed.toLowerCase();
8091
- }
8092
- return false;
8093
- });
8094
- if (found) {
8095
- const id = getString(found.id);
8096
- if (id) {
8097
- return id;
8098
- }
8099
- }
8100
- const meta = isRecord(resp.response_metadata) ? resp.response_metadata : null;
8101
- const next = meta ? getString(meta.next_cursor) : undefined;
8102
- if (!next) {
8103
- break;
8104
- }
8105
- cursor = next;
8106
- }
8107
- return null;
8108
- }
8109
- async function fetchDmMap(client) {
8110
- const map = new Map;
8111
- let cursor;
8112
- for (;; ) {
8113
- const resp = await client.api("conversations.list", {
8114
- types: "im",
8115
- limit: 200,
8116
- cursor
8117
- });
8118
- const channels = asArray(resp.channels).filter(isRecord);
8119
- for (const ch of channels) {
8120
- const id = getString(ch.id);
8121
- const user = getString(ch.user);
8122
- if (id && user) {
8123
- map.set(user, id);
8124
- }
8125
- }
8126
- const meta = isRecord(resp.response_metadata) ? resp.response_metadata : null;
8127
- const next = meta ? getString(meta.next_cursor) : undefined;
8128
- if (!next) {
8129
- break;
8130
- }
8131
- cursor = next;
8132
- }
8133
- return map;
8134
- }
8135
- async function getDmChannelForUsers(client, inputs) {
8136
- if (!inputs || inputs.length === 0) {
8137
- throw new Error("At least one user is required");
8138
- }
8139
- if (inputs.length > 8) {
8140
- throw new Error("Slack supports a maximum of 8 users in a group DM");
8141
- }
8142
- const userIds = [];
8143
- for (const input of inputs) {
8144
- const trimmed = input.trim();
8145
- if (!trimmed) {
8146
- continue;
8147
- }
8148
- const userId = await resolveUserId2(client, trimmed);
8149
- if (!userId) {
8150
- throw new Error(`Could not resolve user: ${input}`);
8151
- }
8152
- userIds.push(userId);
8153
- }
8154
- if (userIds.length === 0) {
8155
- throw new Error("No valid users provided");
8156
- }
8157
- const resp = await client.api("conversations.open", { users: userIds.join(",") });
8158
- const channel = isRecord(resp.channel) ? resp.channel : null;
8159
- const channelId = channel ? getString(channel.id) : null;
8160
- if (!channelId) {
8161
- throw new Error("conversations.open returned no channel");
8162
- }
8163
- const channelType = channelId.startsWith("D") ? "dm" : "group_dm";
8164
- return {
8165
- user_ids: userIds,
8166
- dm_channel_id: channelId,
8167
- channel_type: channelType
8168
- };
8169
- }
8170
- function toCompactUser(u) {
8171
- const profile = isRecord(u.profile) ? u.profile : {};
8172
- return {
8173
- id: getString(u.id) ?? "",
8174
- name: getString(u.name) ?? undefined,
8175
- real_name: getString(u.real_name) ?? getString(profile.real_name) ?? undefined,
8176
- display_name: getString(profile.display_name) ?? undefined,
8177
- email: getString(profile.email) ?? undefined,
8178
- title: getString(profile.title) ?? undefined,
8179
- tz: getString(u.tz) ?? undefined,
8180
- is_bot: typeof u.is_bot === "boolean" ? u.is_bot : undefined,
8181
- deleted: typeof u.deleted === "boolean" ? u.deleted : undefined
8182
- };
8183
- }
8184
-
8185
8531
  // src/cli/user-command.ts
8186
8532
  function registerUserCommand(input) {
8187
8533
  const userCmd = input.program.command("user").description("Workspace user directory");
@@ -8366,7 +8712,7 @@ function registerChannelCommand(input) {
8366
8712
  }
8367
8713
  let userId;
8368
8714
  if (options.user) {
8369
- const resolvedUserId = await resolveUserId2(client, options.user);
8715
+ const resolvedUserId = await resolveUserId(client, options.user);
8370
8716
  if (!resolvedUserId) {
8371
8717
  throw new Error(`Could not resolve user: ${options.user}`);
8372
8718
  }
@@ -8449,7 +8795,7 @@ function registerChannelCommand(input) {
8449
8795
  const resolvedUserIds = [];
8450
8796
  const unresolvedUsers = [];
8451
8797
  for (const userInput of userInputs) {
8452
- const userId = await resolveUserId2(client, userInput);
8798
+ const userId = await resolveUserId(client, userInput);
8453
8799
  if (!userId) {
8454
8800
  unresolvedUsers.push(userInput);
8455
8801
  continue;
@@ -8810,5 +9156,5 @@ if (subcommand && subcommand !== "update") {
8810
9156
  backgroundUpdateCheck();
8811
9157
  }
8812
9158
 
8813
- //# debugId=517A87768FFFD43D64756E2164756E21
9159
+ //# debugId=DD5B850D326A863A64756E2164756E21
8814
9160
  //# sourceMappingURL=index.js.map