agent-slack 0.7.1 → 0.8.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/dist/index.js CHANGED
@@ -3927,6 +3927,412 @@ 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
+ let cursor;
4017
+ for (;; ) {
4018
+ const resp = await client.api("users.list", { limit: 200, cursor });
4019
+ const members = asArray(resp.members).filter(isRecord);
4020
+ const found = members.find((m) => {
4021
+ if (getString(m.name) === handle) {
4022
+ return true;
4023
+ }
4024
+ if (looksLikeEmail) {
4025
+ const profile = isRecord(m.profile) ? m.profile : null;
4026
+ const email = profile ? getString(profile.email) : undefined;
4027
+ return Boolean(email) && email?.toLowerCase() === trimmed.toLowerCase();
4028
+ }
4029
+ return false;
4030
+ });
4031
+ if (found) {
4032
+ const id = getString(found.id);
4033
+ if (id) {
4034
+ return id;
4035
+ }
4036
+ }
4037
+ const meta = isRecord(resp.response_metadata) ? resp.response_metadata : null;
4038
+ const next = meta ? getString(meta.next_cursor) : undefined;
4039
+ if (!next) {
4040
+ break;
4041
+ }
4042
+ cursor = next;
4043
+ }
4044
+ return null;
4045
+ }
4046
+ async function fetchDmMap(client) {
4047
+ const map = new Map;
4048
+ let cursor;
4049
+ for (;; ) {
4050
+ const resp = await client.api("conversations.list", {
4051
+ types: "im",
4052
+ limit: 200,
4053
+ cursor
4054
+ });
4055
+ const channels = asArray(resp.channels).filter(isRecord);
4056
+ for (const ch of channels) {
4057
+ const id = getString(ch.id);
4058
+ const user = getString(ch.user);
4059
+ if (id && user) {
4060
+ map.set(user, id);
4061
+ }
4062
+ }
4063
+ const meta = isRecord(resp.response_metadata) ? resp.response_metadata : null;
4064
+ const next = meta ? getString(meta.next_cursor) : undefined;
4065
+ if (!next) {
4066
+ break;
4067
+ }
4068
+ cursor = next;
4069
+ }
4070
+ return map;
4071
+ }
4072
+ async function getDmChannelForUsers(client, inputs) {
4073
+ if (!inputs || inputs.length === 0) {
4074
+ throw new Error("At least one user is required");
4075
+ }
4076
+ if (inputs.length > 8) {
4077
+ throw new Error("Slack supports a maximum of 8 users in a group DM");
4078
+ }
4079
+ const userIds = [];
4080
+ for (const input of inputs) {
4081
+ const trimmed = input.trim();
4082
+ if (!trimmed) {
4083
+ continue;
4084
+ }
4085
+ const userId = await resolveUserId(client, trimmed);
4086
+ if (!userId) {
4087
+ throw new Error(`Could not resolve user: ${input}`);
4088
+ }
4089
+ userIds.push(userId);
4090
+ }
4091
+ if (userIds.length === 0) {
4092
+ throw new Error("No valid users provided");
4093
+ }
4094
+ const resp = await client.api("conversations.open", { users: userIds.join(",") });
4095
+ const channel = isRecord(resp.channel) ? resp.channel : null;
4096
+ const channelId = channel ? getString(channel.id) : null;
4097
+ if (!channelId) {
4098
+ throw new Error("conversations.open returned no channel");
4099
+ }
4100
+ const channelType = channelId.startsWith("D") ? "dm" : "group_dm";
4101
+ return {
4102
+ user_ids: userIds,
4103
+ dm_channel_id: channelId,
4104
+ channel_type: channelType
4105
+ };
4106
+ }
4107
+ function toCompactUser(u) {
4108
+ const profile = isRecord(u.profile) ? u.profile : {};
4109
+ return {
4110
+ id: getString(u.id) ?? "",
4111
+ name: getString(u.name) ?? undefined,
4112
+ real_name: getString(u.real_name) ?? getString(profile.real_name) ?? undefined,
4113
+ display_name: getString(profile.display_name) ?? undefined,
4114
+ email: getString(profile.email) ?? undefined,
4115
+ title: getString(profile.title) ?? undefined,
4116
+ tz: getString(u.tz) ?? undefined,
4117
+ is_bot: typeof u.is_bot === "boolean" ? u.is_bot : undefined,
4118
+ deleted: typeof u.deleted === "boolean" ? u.deleted : undefined
4119
+ };
4120
+ }
4121
+
4122
+ // src/slack/user-cache.ts
4123
+ var CACHE_VERSION = 1;
4124
+ var USER_TTL_MS = 24 * 60 * 60 * 1000;
4125
+ var USER_ID_PATTERN = /^(U|W)[A-Z0-9]{8,}$/;
4126
+ var USER_MENTION_PATTERN = /<@((U|W)[A-Z0-9]{8,})(?:\|[^>]+)?>/g;
4127
+ async function resolveUsersById(input) {
4128
+ const uniqueIds = dedupeUserIds(input.userIds);
4129
+ if (uniqueIds.length === 0) {
4130
+ return new Map;
4131
+ }
4132
+ const forceRefresh = input.forceRefresh ?? false;
4133
+ const now = Date.now();
4134
+ const workspaceKey = hashWorkspaceUrl(input.workspaceUrl);
4135
+ const isUnknownWorkspace = workspaceKey === "unknown";
4136
+ const cachePath = isUnknownWorkspace ? "" : join13(getAppDir(), `users-cache-${workspaceKey}.json`);
4137
+ const diskCache = cachePath ? await loadCache(cachePath) : { version: CACHE_VERSION, entries: {} };
4138
+ const out = new Map;
4139
+ const missing = [];
4140
+ for (const userId of uniqueIds) {
4141
+ const cached = diskCache.entries[userId];
4142
+ if (!forceRefresh && cached && now - cached.fetched_at < USER_TTL_MS) {
4143
+ out.set(userId, cached.user);
4144
+ continue;
4145
+ }
4146
+ missing.push(userId);
4147
+ }
4148
+ let cacheChanged = false;
4149
+ if (missing.length > 0) {
4150
+ const fetched = [];
4151
+ const concurrency = 5;
4152
+ for (let i = 0;i < missing.length; i += concurrency) {
4153
+ const chunk = missing.slice(i, i + concurrency);
4154
+ const results = await Promise.all(chunk.map(async (userId) => ({ userId, user: await fetchUserById(input.client, userId) })));
4155
+ fetched.push(...results);
4156
+ }
4157
+ for (const item of fetched) {
4158
+ if (!item.user) {
4159
+ continue;
4160
+ }
4161
+ const entry = {
4162
+ fetched_at: now,
4163
+ user: item.user
4164
+ };
4165
+ diskCache.entries[item.userId] = entry;
4166
+ out.set(item.userId, item.user);
4167
+ cacheChanged = true;
4168
+ }
4169
+ }
4170
+ if (cachePath) {
4171
+ const prunedCache = pruneExpiredEntries(diskCache, now);
4172
+ if (Object.keys(diskCache.entries).length !== Object.keys(prunedCache.entries).length) {
4173
+ cacheChanged = true;
4174
+ }
4175
+ if (cacheChanged) {
4176
+ await writeCache(cachePath, prunedCache);
4177
+ }
4178
+ }
4179
+ return out;
4180
+ }
4181
+ function collectReferencedUserIds(messages, options) {
4182
+ const ids = new Set;
4183
+ const includeReactions = options?.includeReactions ?? false;
4184
+ for (const message of messages) {
4185
+ collectUserIdsFromMessage(message, ids, { includeReactions });
4186
+ }
4187
+ return Array.from(ids);
4188
+ }
4189
+ function toReferencedUsers(userIds, usersById) {
4190
+ const out = {};
4191
+ for (const userId of dedupeUserIds(userIds)) {
4192
+ const user = usersById.get(userId);
4193
+ if (!user) {
4194
+ continue;
4195
+ }
4196
+ out[userId] = user;
4197
+ }
4198
+ return Object.keys(out).length > 0 ? out : undefined;
4199
+ }
4200
+ function dedupeUserIds(ids) {
4201
+ const seen = new Set;
4202
+ for (const raw of ids) {
4203
+ const userId = String(raw).trim();
4204
+ if (!USER_ID_PATTERN.test(userId)) {
4205
+ continue;
4206
+ }
4207
+ seen.add(userId);
4208
+ }
4209
+ return Array.from(seen);
4210
+ }
4211
+ function hashWorkspaceUrl(workspaceUrl) {
4212
+ const trimmed = workspaceUrl.trim();
4213
+ if (!trimmed) {
4214
+ return "unknown";
4215
+ }
4216
+ let source = trimmed;
4217
+ try {
4218
+ source = new URL(trimmed).hostname.toLowerCase();
4219
+ } catch {
4220
+ source = trimmed.toLowerCase();
4221
+ }
4222
+ if (!source || source === "unknown") {
4223
+ return "unknown";
4224
+ }
4225
+ return createHash("sha256").update(source).digest("hex").slice(0, 16);
4226
+ }
4227
+ async function loadCache(path) {
4228
+ const file = await readJsonFile(path);
4229
+ if (!file || file.version !== CACHE_VERSION || !isRecord(file.entries)) {
4230
+ return { version: CACHE_VERSION, entries: {} };
4231
+ }
4232
+ const entries = {};
4233
+ for (const [userId, rawEntry] of Object.entries(file.entries)) {
4234
+ if (!USER_ID_PATTERN.test(userId) || !isRecord(rawEntry)) {
4235
+ continue;
4236
+ }
4237
+ const fetchedAt = typeof rawEntry.fetched_at === "number" ? rawEntry.fetched_at : undefined;
4238
+ const user = isRecord(rawEntry.user) ? toCompactUser(rawEntry.user) : null;
4239
+ if (!fetchedAt || !user) {
4240
+ continue;
4241
+ }
4242
+ entries[userId] = { fetched_at: fetchedAt, user };
4243
+ }
4244
+ return {
4245
+ version: CACHE_VERSION,
4246
+ entries
4247
+ };
4248
+ }
4249
+ async function writeCache(path, file) {
4250
+ try {
4251
+ await writeJsonFile(path, file);
4252
+ } catch {}
4253
+ }
4254
+ function pruneExpiredEntries(file, now) {
4255
+ const next = {};
4256
+ for (const [userId, entry] of Object.entries(file.entries)) {
4257
+ if (now - entry.fetched_at >= USER_TTL_MS) {
4258
+ continue;
4259
+ }
4260
+ next[userId] = entry;
4261
+ }
4262
+ return { version: CACHE_VERSION, entries: next };
4263
+ }
4264
+ async function fetchUserById(client, userId) {
4265
+ try {
4266
+ const resp = await client.api("users.info", { user: userId });
4267
+ const user = isRecord(resp.user) ? resp.user : null;
4268
+ if (!user) {
4269
+ return;
4270
+ }
4271
+ return toCompactUser(user);
4272
+ } catch {
4273
+ return;
4274
+ }
4275
+ }
4276
+ function collectUserIdsFromUnknown(value, out) {
4277
+ if (typeof value === "string") {
4278
+ collectMentionUserIds(value, out);
4279
+ return;
4280
+ }
4281
+ if (Array.isArray(value)) {
4282
+ for (const item of value) {
4283
+ collectUserIdsFromUnknown(item, out);
4284
+ }
4285
+ return;
4286
+ }
4287
+ if (!isRecord(value)) {
4288
+ return;
4289
+ }
4290
+ for (const [key, child] of Object.entries(value)) {
4291
+ if ((key === "user" || key === "user_id") && typeof child === "string") {
4292
+ if (USER_ID_PATTERN.test(child)) {
4293
+ out.add(child);
4294
+ }
4295
+ continue;
4296
+ }
4297
+ if (key === "users") {
4298
+ for (const maybeUserId of asArray(child)) {
4299
+ const userId = String(maybeUserId);
4300
+ if (USER_ID_PATTERN.test(userId)) {
4301
+ out.add(userId);
4302
+ }
4303
+ }
4304
+ continue;
4305
+ }
4306
+ collectUserIdsFromUnknown(child, out);
4307
+ }
4308
+ }
4309
+ function collectUserIdsFromMessage(message, out, options) {
4310
+ if (message.user && USER_ID_PATTERN.test(message.user)) {
4311
+ out.add(message.user);
4312
+ }
4313
+ if (typeof message.text === "string") {
4314
+ collectMentionUserIds(message.text, out);
4315
+ }
4316
+ collectUserIdsFromUnknown(message.blocks, out);
4317
+ collectUserIdsFromUnknown(message.attachments, out);
4318
+ if (options.includeReactions) {
4319
+ collectUserIdsFromUnknown(message.reactions, out);
4320
+ }
4321
+ }
4322
+ function collectMentionUserIds(text, out) {
4323
+ USER_MENTION_PATTERN.lastIndex = 0;
4324
+ for (;; ) {
4325
+ const match = USER_MENTION_PATTERN.exec(text);
4326
+ if (!match) {
4327
+ break;
4328
+ }
4329
+ const userId = match[1] ?? "";
4330
+ if (USER_ID_PATTERN.test(userId)) {
4331
+ out.add(userId);
4332
+ }
4333
+ }
4334
+ }
4335
+
3930
4336
  // src/cli/message-read-actions.ts
3931
4337
  async function handleMessageGet(input) {
3932
4338
  const target = parseMsgTarget(input.targetInput);
@@ -3946,8 +4352,25 @@ async function handleMessageGet(input) {
3946
4352
  const thread2 = await getThreadSummary(client2, { channelId: ref2.channel_id, msg: msg2 });
3947
4353
  const downloadedPaths2 = await downloadMessageFiles({ auth: auth2, messages: [msg2] });
3948
4354
  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 });
4355
+ const referencedUserIds2 = collectReferencedUserIds([msg2], {
4356
+ includeReactions: includeReactions2
4357
+ });
4358
+ const usersById2 = input.options.resolveUsers || input.options.refreshUsers ? await resolveUsersById({
4359
+ client: client2,
4360
+ workspaceUrl: ref2.workspace_url,
4361
+ userIds: referencedUserIds2,
4362
+ forceRefresh: Boolean(input.options.refreshUsers)
4363
+ }) : new Map;
4364
+ const message2 = toCompactMessage(msg2, {
4365
+ maxBodyChars: maxBodyChars2,
4366
+ includeReactions: includeReactions2,
4367
+ downloadedPaths: downloadedPaths2
4368
+ });
4369
+ return pruneEmpty({
4370
+ message: message2,
4371
+ thread: thread2,
4372
+ referenced_users: toReferencedUsers(referencedUserIds2, usersById2)
4373
+ });
3951
4374
  }
3952
4375
  const ts = input.options.ts?.trim();
3953
4376
  if (!ts) {
@@ -3971,8 +4394,25 @@ async function handleMessageGet(input) {
3971
4394
  const thread = await getThreadSummary(client, { channelId, msg });
3972
4395
  const downloadedPaths = await downloadMessageFiles({ auth, messages: [msg] });
3973
4396
  const maxBodyChars = Number.parseInt(input.options.maxBodyChars, 10);
3974
- const message = toCompactMessage(msg, { maxBodyChars, includeReactions, downloadedPaths });
3975
- return pruneEmpty({ message, thread });
4397
+ const referencedUserIds = collectReferencedUserIds([msg], {
4398
+ includeReactions
4399
+ });
4400
+ const usersById = input.options.resolveUsers || input.options.refreshUsers ? await resolveUsersById({
4401
+ client,
4402
+ workspaceUrl: ref.workspace_url,
4403
+ userIds: referencedUserIds,
4404
+ forceRefresh: Boolean(input.options.refreshUsers)
4405
+ }) : new Map;
4406
+ const message = toCompactMessage(msg, {
4407
+ maxBodyChars,
4408
+ includeReactions,
4409
+ downloadedPaths
4410
+ });
4411
+ return pruneEmpty({
4412
+ message,
4413
+ thread,
4414
+ referenced_users: toReferencedUsers(referencedUserIds, usersById)
4415
+ });
3976
4416
  }
3977
4417
  });
3978
4418
  }
@@ -4005,8 +4445,18 @@ async function handleMessageList(input) {
4005
4445
  });
4006
4446
  const downloadedPaths2 = await downloadMessageFiles({ auth: auth2, messages: threadMessages2 });
4007
4447
  const maxBodyChars2 = Number.parseInt(input.options.maxBodyChars, 10);
4448
+ const referencedUserIds2 = collectReferencedUserIds(threadMessages2, {
4449
+ includeReactions: includeReactions2
4450
+ });
4451
+ const usersById2 = input.options.resolveUsers || input.options.refreshUsers ? await resolveUsersById({
4452
+ client: client2,
4453
+ workspaceUrl: ref.workspace_url,
4454
+ userIds: referencedUserIds2,
4455
+ forceRefresh: Boolean(input.options.refreshUsers)
4456
+ }) : new Map;
4008
4457
  return pruneEmpty({
4009
- messages: threadMessages2.map((m) => toCompactMessage(m, { maxBodyChars: maxBodyChars2, includeReactions: includeReactions2, downloadedPaths: downloadedPaths2 })).map(toThreadListMessage)
4458
+ messages: threadMessages2.map((m) => toCompactMessage(m, { maxBodyChars: maxBodyChars2, includeReactions: includeReactions2, downloadedPaths: downloadedPaths2 })).map(toThreadListMessage),
4459
+ referenced_users: toReferencedUsers(referencedUserIds2, usersById2)
4010
4460
  });
4011
4461
  }
4012
4462
  const { client, auth, workspace_url } = await input.ctx.getClientForWorkspace(workspaceUrl);
@@ -4036,9 +4486,19 @@ async function handleMessageList(input) {
4036
4486
  });
4037
4487
  const downloadedPaths2 = await downloadMessageFiles({ auth, messages: channelMessages });
4038
4488
  const maxBodyChars2 = Number.parseInt(input.options.maxBodyChars, 10);
4489
+ const referencedUserIds2 = collectReferencedUserIds(channelMessages, {
4490
+ includeReactions: includeReactions2
4491
+ });
4492
+ const usersById2 = input.options.resolveUsers || input.options.refreshUsers ? await resolveUsersById({
4493
+ client,
4494
+ workspaceUrl: workspace_url ?? workspaceUrl ?? "",
4495
+ userIds: referencedUserIds2,
4496
+ forceRefresh: Boolean(input.options.refreshUsers)
4497
+ }) : new Map;
4039
4498
  return pruneEmpty({
4040
4499
  channel_id: channelId,
4041
- messages: channelMessages.map((m) => toCompactMessage(m, { maxBodyChars: maxBodyChars2, includeReactions: includeReactions2, downloadedPaths: downloadedPaths2 }))
4500
+ messages: channelMessages.map((m) => toCompactMessage(m, { maxBodyChars: maxBodyChars2, includeReactions: includeReactions2, downloadedPaths: downloadedPaths2 })),
4501
+ referenced_users: toReferencedUsers(referencedUserIds2, usersById2)
4042
4502
  });
4043
4503
  }
4044
4504
  if (hasReactionFilters) {
@@ -4063,8 +4523,18 @@ async function handleMessageList(input) {
4063
4523
  });
4064
4524
  const downloadedPaths = await downloadMessageFiles({ auth, messages: threadMessages });
4065
4525
  const maxBodyChars = Number.parseInt(input.options.maxBodyChars, 10);
4526
+ const referencedUserIds = collectReferencedUserIds(threadMessages, {
4527
+ includeReactions
4528
+ });
4529
+ const usersById = input.options.resolveUsers || input.options.refreshUsers ? await resolveUsersById({
4530
+ client,
4531
+ workspaceUrl: workspace_url ?? workspaceUrl ?? "",
4532
+ userIds: referencedUserIds,
4533
+ forceRefresh: Boolean(input.options.refreshUsers)
4534
+ }) : new Map;
4066
4535
  return pruneEmpty({
4067
- messages: threadMessages.map((m) => toCompactMessage(m, { maxBodyChars, includeReactions, downloadedPaths })).map(toThreadListMessage)
4536
+ messages: threadMessages.map((m) => toCompactMessage(m, { maxBodyChars, includeReactions, downloadedPaths })).map(toThreadListMessage),
4537
+ referenced_users: toReferencedUsers(referencedUserIds, usersById)
4068
4538
  });
4069
4539
  }
4070
4540
  });
@@ -6357,7 +6827,7 @@ function collectOptionValue(value, previous = []) {
6357
6827
  }
6358
6828
  function registerMessageCommand(input) {
6359
6829
  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) => {
6830
+ 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
6831
  const [targetInput, options] = args;
6362
6832
  try {
6363
6833
  const payload = await handleMessageGet({ ctx: input.ctx, targetInput, options });
@@ -6367,7 +6837,7 @@ function registerMessageCommand(input) {
6367
6837
  process.exitCode = 1;
6368
6838
  }
6369
6839
  });
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) => {
6840
+ 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
6841
  const [targetInput, options] = args;
6372
6842
  try {
6373
6843
  const payload = await handleMessageList({ ctx: input.ctx, targetInput, options });
@@ -6559,7 +7029,7 @@ async function channelTokenForSearch(client, channel) {
6559
7029
  } catch {}
6560
7030
  return null;
6561
7031
  }
6562
- async function resolveUserId(client, input) {
7032
+ async function resolveUserId2(client, input) {
6563
7033
  const trimmed = input.trim();
6564
7034
  if (!trimmed) {
6565
7035
  return;
@@ -6747,7 +7217,7 @@ async function searchFilesViaSearchApi(client, input) {
6747
7217
  }
6748
7218
  async function searchFilesInChannelsFallback(client, input) {
6749
7219
  const channelIds = await Promise.all(input.channels.map((c) => resolveChannelId(client, c)));
6750
- const userId = input.user ? await resolveUserId(client, input.user) : undefined;
7220
+ const userId = input.user ? await resolveUserId2(client, input.user) : undefined;
6751
7221
  const queryLower = input.query.trim().toLowerCase();
6752
7222
  const ts_from = input.after ? dateToUnixSeconds(input.after, "start") : undefined;
6753
7223
  const ts_to = input.before ? dateToUnixSeconds(input.before, "end") : undefined;
@@ -6845,7 +7315,7 @@ function passesFileContentTypeFilter(f, contentType) {
6845
7315
  async function searchMessagesViaSearchApi(client, input) {
6846
7316
  const matches = input.rawMatches;
6847
7317
  if (matches.length === 0) {
6848
- return [];
7318
+ return { messages: [] };
6849
7319
  }
6850
7320
  const messageRefs = [];
6851
7321
  for (const m of matches) {
@@ -6869,6 +7339,7 @@ async function searchMessagesViaSearchApi(client, input) {
6869
7339
  }
6870
7340
  const downloadedPaths = {};
6871
7341
  const downloadsDir = input.download ? await ensureDownloadsDir() : null;
7342
+ const resolvedMessages = [];
6872
7343
  const out = [];
6873
7344
  for (const ref of messageRefs) {
6874
7345
  let full = null;
@@ -6907,21 +7378,36 @@ async function searchMessagesViaSearchApi(client, input) {
6907
7378
  if (!passesContentTypeFilter(compact, input.contentType)) {
6908
7379
  continue;
6909
7380
  }
6910
- out.push(stripThreadListFields(compact));
7381
+ resolvedMessages.push(full);
7382
+ out.push(toSearchCompactMessage(compact, ref.permalink));
6911
7383
  if (out.length >= input.limit) {
6912
7384
  break;
6913
7385
  }
6914
7386
  }
6915
- return out;
7387
+ const referencedUserIds = collectReferencedUserIds(resolvedMessages, {
7388
+ includeReactions: false
7389
+ });
7390
+ const shouldResolveUsers = input.resolveUsers || input.refreshUsers;
7391
+ const usersById = shouldResolveUsers ? await resolveUsersById({
7392
+ client,
7393
+ workspaceUrl: input.workspace_url ?? "",
7394
+ userIds: referencedUserIds,
7395
+ forceRefresh: Boolean(input.refreshUsers)
7396
+ }) : new Map;
7397
+ return {
7398
+ messages: out,
7399
+ referenced_users: toReferencedUsers(referencedUserIds, usersById)
7400
+ };
6916
7401
  }
6917
7402
  async function searchMessagesInChannelsFallback(client, input) {
6918
7403
  const channelIds = await Promise.all(input.channels.map((c) => resolveChannelId(client, c)));
6919
7404
  const queryLower = input.query.trim().toLowerCase();
6920
- const userId = input.user ? await resolveUserId(client, input.user) : undefined;
7405
+ const userId = input.user ? await resolveUserId2(client, input.user) : undefined;
6921
7406
  const afterSec = input.after ? dateToUnixSeconds(input.after, "start") : null;
6922
7407
  const beforeSec = input.before ? dateToUnixSeconds(input.before, "end") : null;
6923
7408
  const downloadsDir = input.download ? await ensureDownloadsDir() : null;
6924
7409
  const downloadedPaths = {};
7410
+ const matchedSummaries = [];
6925
7411
  const results = [];
6926
7412
  for (const channelId of channelIds) {
6927
7413
  let cursorLatest;
@@ -6969,9 +7455,22 @@ async function searchMessagesInChannelsFallback(client, input) {
6969
7455
  if (!passesContentTypeFilter(compact, input.contentType)) {
6970
7456
  continue;
6971
7457
  }
6972
- results.push(stripThreadListFields(compact));
7458
+ matchedSummaries.push(summary);
7459
+ results.push(toSearchCompactMessage(compact));
6973
7460
  if (results.length >= input.limit) {
6974
- return results;
7461
+ const referencedUserIds2 = collectReferencedUserIds(matchedSummaries, {
7462
+ includeReactions: false
7463
+ });
7464
+ const usersById2 = await resolveUsersById({
7465
+ client,
7466
+ workspaceUrl: input.workspace_url ?? "",
7467
+ userIds: referencedUserIds2,
7468
+ forceRefresh: Boolean(input.refreshUsers)
7469
+ });
7470
+ return {
7471
+ messages: results,
7472
+ referenced_users: toReferencedUsers(referencedUserIds2, usersById2)
7473
+ };
6975
7474
  }
6976
7475
  }
6977
7476
  if (!cursorLatest) {
@@ -6984,7 +7483,20 @@ async function searchMessagesInChannelsFallback(client, input) {
6984
7483
  }
6985
7484
  }
6986
7485
  }
6987
- return results;
7486
+ const referencedUserIds = collectReferencedUserIds(matchedSummaries, {
7487
+ includeReactions: false
7488
+ });
7489
+ const shouldResolveUsers = input.resolveUsers || input.refreshUsers;
7490
+ const usersById = shouldResolveUsers ? await resolveUsersById({
7491
+ client,
7492
+ workspaceUrl: input.workspace_url ?? "",
7493
+ userIds: referencedUserIds,
7494
+ forceRefresh: Boolean(input.refreshUsers)
7495
+ }) : new Map;
7496
+ return {
7497
+ messages: results,
7498
+ referenced_users: toReferencedUsers(referencedUserIds, usersById)
7499
+ };
6988
7500
  }
6989
7501
  function passesContentTypeFilter(m, contentType) {
6990
7502
  if (contentType === "any") {
@@ -7008,9 +7520,9 @@ function passesContentTypeFilter(m, contentType) {
7008
7520
  }
7009
7521
  return true;
7010
7522
  }
7011
- function stripThreadListFields(m) {
7012
- const { channel_id: _channelId, thread_ts: _threadTs, ...rest } = m;
7013
- return rest;
7523
+ function toSearchCompactMessage(m, permalink) {
7524
+ const { thread_ts: _threadTs, ...rest } = m;
7525
+ return permalink ? { ...rest, permalink } : rest;
7014
7526
  }
7015
7527
  async function downloadFilesForMessage(input) {
7016
7528
  for (const f of input.message.files ?? []) {
@@ -7102,8 +7614,9 @@ async function searchSlack(input) {
7102
7614
  const out = {};
7103
7615
  if (input.options.kind === "messages" || input.options.kind === "all") {
7104
7616
  if (input.options.channels?.length) {
7105
- out.messages = await searchMessagesInChannelsFallback(input.client, {
7617
+ const messageResult = await searchMessagesInChannelsFallback(input.client, {
7106
7618
  auth: input.auth,
7619
+ workspace_url: input.options.workspace_url,
7107
7620
  query: input.options.query,
7108
7621
  channels: input.options.channels,
7109
7622
  user: input.options.user,
@@ -7112,11 +7625,15 @@ async function searchSlack(input) {
7112
7625
  limit,
7113
7626
  maxContentChars,
7114
7627
  contentType,
7115
- download
7628
+ download,
7629
+ resolveUsers: input.options.resolve_users,
7630
+ refreshUsers: input.options.refresh_users
7116
7631
  });
7632
+ out.messages = messageResult.messages;
7633
+ out.referenced_users = messageResult.referenced_users;
7117
7634
  } else {
7118
7635
  const rawMatches = await searchMessagesRaw(input.client, { query: slackQuery, limit });
7119
- out.messages = await searchMessagesViaSearchApi(input.client, {
7636
+ const messageResult = await searchMessagesViaSearchApi(input.client, {
7120
7637
  auth: input.auth,
7121
7638
  workspace_url: input.options.workspace_url,
7122
7639
  slack_query: slackQuery,
@@ -7124,8 +7641,12 @@ async function searchSlack(input) {
7124
7641
  maxContentChars,
7125
7642
  contentType,
7126
7643
  download,
7127
- rawMatches
7644
+ rawMatches,
7645
+ resolveUsers: input.options.resolve_users,
7646
+ refreshUsers: input.options.refresh_users
7128
7647
  });
7648
+ out.messages = messageResult.messages;
7649
+ out.referenced_users = messageResult.referenced_users;
7129
7650
  }
7130
7651
  }
7131
7652
  if (input.options.kind === "files" || input.options.kind === "all") {
@@ -7156,7 +7677,7 @@ async function searchSlack(input) {
7156
7677
 
7157
7678
  // src/cli/search-command.ts
7158
7679
  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");
7680
+ 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
7681
  }
7161
7682
  async function runSearch(input) {
7162
7683
  const workspaceUrl = input.ctx.effectiveWorkspaceUrl(input.options.workspace);
@@ -7183,7 +7704,9 @@ async function runSearch(input) {
7183
7704
  content_type: contentType,
7184
7705
  limit,
7185
7706
  max_content_chars: maxContentChars,
7186
- download: true
7707
+ download: true,
7708
+ resolve_users: Boolean(input.options.resolveUsers || input.options.refreshUsers),
7709
+ refresh_users: Boolean(input.options.refreshUsers)
7187
7710
  }
7188
7711
  });
7189
7712
  }
@@ -7227,13 +7750,13 @@ async function fetchLaterItems(client, options) {
7227
7750
  const pageRawItems = asArray2(resp.saved_items).filter(isRecord5);
7228
7751
  allRawItems.push(...pageRawItems);
7229
7752
  const meta = isRecord5(resp.response_metadata) ? resp.response_metadata : null;
7230
- nextCursor = meta ? getString4(meta.next_cursor) : undefined;
7753
+ nextCursor = meta ? getString5(meta.next_cursor) : undefined;
7231
7754
  if (countsOnly) {
7232
7755
  break;
7233
7756
  }
7234
- let filtered2 = allRawItems.filter((item) => getString4(item.item_type) === "message");
7757
+ let filtered2 = allRawItems.filter((item) => getString5(item.item_type) === "message");
7235
7758
  if (stateFilter !== "all") {
7236
- filtered2 = filtered2.filter((item) => getString4(item.state) === stateFilter);
7759
+ filtered2 = filtered2.filter((item) => getString5(item.state) === stateFilter);
7237
7760
  }
7238
7761
  if (filtered2.length >= limit || !nextCursor) {
7239
7762
  break;
@@ -7253,15 +7776,15 @@ async function fetchLaterItems(client, options) {
7253
7776
  if (countsOnly) {
7254
7777
  return result;
7255
7778
  }
7256
- let filtered = allRawItems.filter((item) => getString4(item.item_type) === "message");
7779
+ let filtered = allRawItems.filter((item) => getString5(item.item_type) === "message");
7257
7780
  if (stateFilter !== "all") {
7258
- filtered = filtered.filter((item) => getString4(item.state) === stateFilter);
7781
+ filtered = filtered.filter((item) => getString5(item.state) === stateFilter);
7259
7782
  }
7260
7783
  filtered = filtered.slice(0, limit);
7261
7784
  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";
7785
+ const channelId = getString5(item.item_id) ?? "";
7786
+ const ts = getString5(item.ts) ?? "";
7787
+ const state = getString5(item.state) ?? "in_progress";
7265
7788
  const dateSaved = getNumber3(item.date_created) ?? 0;
7266
7789
  const dateCompleted = getNumber3(item.date_completed);
7267
7790
  let channelName;
@@ -7272,15 +7795,15 @@ async function fetchLaterItems(client, options) {
7272
7795
  });
7273
7796
  const ch = isRecord5(info.channel) ? info.channel : null;
7274
7797
  if (ch) {
7275
- channelName = getString4(ch.name) ?? getString4(ch.name_normalized) ?? undefined;
7798
+ channelName = getString5(ch.name) ?? getString5(ch.name_normalized) ?? undefined;
7276
7799
  if (ch.is_im && !channelName) {
7277
- const userId = getString4(ch.user);
7800
+ const userId = getString5(ch.user);
7278
7801
  if (userId) {
7279
7802
  try {
7280
7803
  const userInfo = await client.api("users.info", { user: userId });
7281
7804
  const u = isRecord5(userInfo.user) ? userInfo.user : null;
7282
7805
  const profile = u && isRecord5(u.profile) ? u.profile : null;
7283
- channelName = getString4(profile?.display_name) || getString4(u?.real_name) || getString4(u?.name) || undefined;
7806
+ channelName = getString5(profile?.display_name) || getString5(u?.real_name) || getString5(u?.name) || undefined;
7284
7807
  } catch {}
7285
7808
  }
7286
7809
  }
@@ -7295,18 +7818,18 @@ async function fetchLaterItems(client, options) {
7295
7818
  limit: 1
7296
7819
  });
7297
7820
  const msgs = asArray2(history.messages).filter(isRecord5);
7298
- const msg = msgs.find((m) => getString4(m.ts) === ts);
7821
+ const msg = msgs.find((m) => getString5(m.ts) === ts);
7299
7822
  if (msg) {
7300
7823
  const rendered = renderSlackMessageContent(msg);
7301
7824
  const content = maxBodyChars >= 0 && rendered.length > maxBodyChars ? `${rendered.slice(0, maxBodyChars)}
7302
7825
  …` : rendered;
7303
7826
  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
7827
+ author: getString5(msg.user) || getString5(msg.bot_id) ? {
7828
+ user_id: getString5(msg.user) ?? undefined,
7829
+ bot_id: getString5(msg.bot_id) ?? undefined
7307
7830
  } : undefined,
7308
7831
  content: content || undefined,
7309
- thread_ts: getString4(msg.thread_ts) ?? undefined,
7832
+ thread_ts: getString5(msg.thread_ts) ?? undefined,
7310
7833
  reply_count: getNumber3(msg.reply_count) ?? undefined
7311
7834
  };
7312
7835
  }
@@ -7404,7 +7927,7 @@ function isRecord5(value) {
7404
7927
  function asArray2(value) {
7405
7928
  return Array.isArray(value) ? value : [];
7406
7929
  }
7407
- function getString4(value) {
7930
+ function getString5(value) {
7408
7931
  return typeof value === "string" ? value : undefined;
7409
7932
  }
7410
7933
  function getNumber3(value) {
@@ -7622,22 +8145,22 @@ async function fetchUnreads(client, options) {
7622
8145
  });
7623
8146
  const ch = isRecord6(info.channel) ? info.channel : null;
7624
8147
  if (ch) {
7625
- name = getString5(ch.name) ?? getString5(ch.name_normalized) ?? undefined;
8148
+ name = getString6(ch.name) ?? getString6(ch.name_normalized) ?? undefined;
7626
8149
  if (ch.is_im) {
7627
8150
  type = "dm";
7628
- const userId = getString5(ch.user);
8151
+ const userId = getString6(ch.user);
7629
8152
  if (userId && !name) {
7630
8153
  try {
7631
8154
  const userInfo = await client.api("users.info", { user: userId });
7632
8155
  const u = isRecord6(userInfo.user) ? userInfo.user : null;
7633
8156
  const profile = u && isRecord6(u.profile) ? u.profile : null;
7634
- name = getString5(profile?.display_name) || getString5(u?.real_name) || getString5(u?.name) || undefined;
8157
+ name = getString6(profile?.display_name) || getString6(u?.real_name) || getString6(u?.name) || undefined;
7635
8158
  } catch {}
7636
8159
  }
7637
8160
  } else if (ch.is_mpim) {
7638
8161
  type = "mpim";
7639
8162
  } else if (ch.is_group || ch.is_private) {
7640
- type = "group";
8163
+ type = "channel";
7641
8164
  } else {
7642
8165
  type = "channel";
7643
8166
  }
@@ -7659,7 +8182,7 @@ async function fetchUnreads(client, options) {
7659
8182
  let msgs = asArray3(history.messages).filter(isRecord6);
7660
8183
  if (skipSystem) {
7661
8184
  msgs = msgs.filter((m) => {
7662
- const subtype = getString5(m.subtype);
8185
+ const subtype = getString6(m.subtype);
7663
8186
  if (!subtype) {
7664
8187
  return true;
7665
8188
  }
@@ -7693,13 +8216,13 @@ async function fetchUnreads(client, options) {
7693
8216
  const content = maxBodyChars >= 0 && rendered.length > maxBodyChars ? `${rendered.slice(0, maxBodyChars)}
7694
8217
  ...` : rendered;
7695
8218
  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
8219
+ ts: getString6(m.ts) ?? "",
8220
+ author: getString6(m.user) || getString6(m.bot_id) ? {
8221
+ user_id: getString6(m.user) ?? undefined,
8222
+ bot_id: getString6(m.bot_id) ?? undefined
7700
8223
  } : undefined,
7701
8224
  content: content || undefined,
7702
- thread_ts: getString5(m.thread_ts) ?? undefined,
8225
+ thread_ts: getString6(m.thread_ts) ?? undefined,
7703
8226
  reply_count: getNumber4(m.reply_count) ?? undefined
7704
8227
  };
7705
8228
  });
@@ -7737,7 +8260,7 @@ function isRecord6(value) {
7737
8260
  function asArray3(value) {
7738
8261
  return Array.isArray(value) ? value : [];
7739
8262
  }
7740
- function getString5(value) {
8263
+ function getString6(value) {
7741
8264
  return typeof value === "string" ? value : undefined;
7742
8265
  }
7743
8266
  function getNumber4(value) {
@@ -7771,14 +8294,14 @@ function registerUnreadsCommand(input) {
7771
8294
 
7772
8295
  // src/lib/update.ts
7773
8296
  import { execSync as execSync2 } from "node:child_process";
7774
- import { createHash } from "node:crypto";
8297
+ import { createHash as createHash2 } from "node:crypto";
7775
8298
  import { chmod, copyFile as copyFile2, mkdir as mkdir5, readFile as readFile7, rename, rm as rm3, writeFile as writeFile4 } from "node:fs/promises";
7776
8299
  import { tmpdir as tmpdir5 } from "node:os";
7777
- import { basename as basename3, join as join13 } from "node:path";
8300
+ import { basename as basename3, join as join14 } from "node:path";
7778
8301
  var REPO = "stablyai/agent-slack";
7779
8302
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
7780
8303
  function getCachePath() {
7781
- return join13(getAppDir(), "update-check.json");
8304
+ return join14(getAppDir(), "update-check.json");
7782
8305
  }
7783
8306
  function compareSemver(a, b) {
7784
8307
  const pa = a.replace(/^v/, "").split(".").map(Number);
@@ -7874,16 +8397,16 @@ function detectPlatformAsset() {
7874
8397
  }
7875
8398
  async function sha256(filePath) {
7876
8399
  const data = await readFile7(filePath);
7877
- return createHash("sha256").update(data).digest("hex");
8400
+ return createHash2("sha256").update(data).digest("hex");
7878
8401
  }
7879
8402
  async function performUpdate(latest) {
7880
8403
  const asset = detectPlatformAsset();
7881
8404
  const tag = `v${latest}`;
7882
8405
  const baseUrl = `https://github.com/${REPO}/releases/download/${tag}`;
7883
- const tmp = join13(tmpdir5(), `agent-slack-update-${Date.now()}`);
8406
+ const tmp = join14(tmpdir5(), `agent-slack-update-${Date.now()}`);
7884
8407
  await mkdir5(tmp, { recursive: true });
7885
- const binTmp = join13(tmp, asset);
7886
- const sumsTmp = join13(tmp, "checksums-sha256.txt");
8408
+ const binTmp = join14(tmp, asset);
8409
+ const sumsTmp = join14(tmp, "checksums-sha256.txt");
7887
8410
  try {
7888
8411
  const [binResp, sumsResp] = await Promise.all([
7889
8412
  fetch(`${baseUrl}/${asset}`, { signal: AbortSignal.timeout(120000) }),
@@ -7994,194 +8517,6 @@ function registerUpdateCommand(input) {
7994
8517
  });
7995
8518
  }
7996
8519
 
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
8520
  // src/cli/user-command.ts
8186
8521
  function registerUserCommand(input) {
8187
8522
  const userCmd = input.program.command("user").description("Workspace user directory");
@@ -8366,7 +8701,7 @@ function registerChannelCommand(input) {
8366
8701
  }
8367
8702
  let userId;
8368
8703
  if (options.user) {
8369
- const resolvedUserId = await resolveUserId2(client, options.user);
8704
+ const resolvedUserId = await resolveUserId(client, options.user);
8370
8705
  if (!resolvedUserId) {
8371
8706
  throw new Error(`Could not resolve user: ${options.user}`);
8372
8707
  }
@@ -8449,7 +8784,7 @@ function registerChannelCommand(input) {
8449
8784
  const resolvedUserIds = [];
8450
8785
  const unresolvedUsers = [];
8451
8786
  for (const userInput of userInputs) {
8452
- const userId = await resolveUserId2(client, userInput);
8787
+ const userId = await resolveUserId(client, userInput);
8453
8788
  if (!userId) {
8454
8789
  unresolvedUsers.push(userInput);
8455
8790
  continue;
@@ -8810,5 +9145,5 @@ if (subcommand && subcommand !== "update") {
8810
9145
  backgroundUpdateCheck();
8811
9146
  }
8812
9147
 
8813
- //# debugId=517A87768FFFD43D64756E2164756E21
9148
+ //# debugId=4F295E0EDEFC23F164756E2164756E21
8814
9149
  //# sourceMappingURL=index.js.map