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 +588 -253
- package/dist/index.js.map +13 -12
- package/package.json +1 -1
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
|
|
3950
|
-
|
|
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
|
|
3975
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
7458
|
+
matchedSummaries.push(summary);
|
|
7459
|
+
results.push(toSearchCompactMessage(compact));
|
|
6973
7460
|
if (results.length >= input.limit) {
|
|
6974
|
-
|
|
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
|
-
|
|
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
|
|
7012
|
-
const {
|
|
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
|
-
|
|
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
|
-
|
|
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 ?
|
|
7753
|
+
nextCursor = meta ? getString5(meta.next_cursor) : undefined;
|
|
7231
7754
|
if (countsOnly) {
|
|
7232
7755
|
break;
|
|
7233
7756
|
}
|
|
7234
|
-
let filtered2 = allRawItems.filter((item) =>
|
|
7757
|
+
let filtered2 = allRawItems.filter((item) => getString5(item.item_type) === "message");
|
|
7235
7758
|
if (stateFilter !== "all") {
|
|
7236
|
-
filtered2 = filtered2.filter((item) =>
|
|
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) =>
|
|
7779
|
+
let filtered = allRawItems.filter((item) => getString5(item.item_type) === "message");
|
|
7257
7780
|
if (stateFilter !== "all") {
|
|
7258
|
-
filtered = filtered.filter((item) =>
|
|
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 =
|
|
7263
|
-
const ts =
|
|
7264
|
-
const state =
|
|
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 =
|
|
7798
|
+
channelName = getString5(ch.name) ?? getString5(ch.name_normalized) ?? undefined;
|
|
7276
7799
|
if (ch.is_im && !channelName) {
|
|
7277
|
-
const userId =
|
|
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 =
|
|
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) =>
|
|
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:
|
|
7305
|
-
user_id:
|
|
7306
|
-
bot_id:
|
|
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:
|
|
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
|
|
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 =
|
|
8148
|
+
name = getString6(ch.name) ?? getString6(ch.name_normalized) ?? undefined;
|
|
7626
8149
|
if (ch.is_im) {
|
|
7627
8150
|
type = "dm";
|
|
7628
|
-
const userId =
|
|
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 =
|
|
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 = "
|
|
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 =
|
|
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:
|
|
7697
|
-
author:
|
|
7698
|
-
user_id:
|
|
7699
|
-
bot_id:
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
8406
|
+
const tmp = join14(tmpdir5(), `agent-slack-update-${Date.now()}`);
|
|
7884
8407
|
await mkdir5(tmp, { recursive: true });
|
|
7885
|
-
const binTmp =
|
|
7886
|
-
const sumsTmp =
|
|
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
|
|
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
|
|
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=
|
|
9148
|
+
//# debugId=4F295E0EDEFC23F164756E2164756E21
|
|
8814
9149
|
//# sourceMappingURL=index.js.map
|