@urugus/slack-cli 0.1.7 → 0.1.8

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.
Files changed (63) hide show
  1. package/dist/commands/channels.d.ts.map +1 -1
  2. package/dist/commands/channels.js +3 -12
  3. package/dist/commands/channels.js.map +1 -1
  4. package/dist/commands/config-subcommands.d.ts +14 -0
  5. package/dist/commands/config-subcommands.d.ts.map +1 -0
  6. package/dist/commands/config-subcommands.js +65 -0
  7. package/dist/commands/config-subcommands.js.map +1 -0
  8. package/dist/commands/config.d.ts.map +1 -1
  9. package/dist/commands/config.js +7 -55
  10. package/dist/commands/config.js.map +1 -1
  11. package/dist/commands/history-display.d.ts +3 -0
  12. package/dist/commands/history-display.d.ts.map +1 -0
  13. package/dist/commands/history-display.js +33 -0
  14. package/dist/commands/history-display.js.map +1 -0
  15. package/dist/commands/history-validators.d.ts +5 -0
  16. package/dist/commands/history-validators.d.ts.map +1 -0
  17. package/dist/commands/history-validators.js +35 -0
  18. package/dist/commands/history-validators.js.map +1 -0
  19. package/dist/commands/history.d.ts.map +1 -1
  20. package/dist/commands/history.js +8 -51
  21. package/dist/commands/history.js.map +1 -1
  22. package/dist/utils/channel-formatter.d.ts +0 -3
  23. package/dist/utils/channel-formatter.d.ts.map +1 -1
  24. package/dist/utils/channel-formatter.js +8 -44
  25. package/dist/utils/channel-formatter.js.map +1 -1
  26. package/dist/utils/formatters/channels-list-formatters.d.ts +13 -0
  27. package/dist/utils/formatters/channels-list-formatters.d.ts.map +1 -0
  28. package/dist/utils/formatters/channels-list-formatters.js +53 -0
  29. package/dist/utils/formatters/channels-list-formatters.js.map +1 -0
  30. package/dist/utils/slack-api-client.d.ts +2 -3
  31. package/dist/utils/slack-api-client.d.ts.map +1 -1
  32. package/dist/utils/slack-api-client.js +9 -153
  33. package/dist/utils/slack-api-client.js.map +1 -1
  34. package/dist/utils/slack-operations/base-client.d.ts +10 -0
  35. package/dist/utils/slack-operations/base-client.d.ts.map +1 -0
  36. package/dist/utils/slack-operations/base-client.js +32 -0
  37. package/dist/utils/slack-operations/base-client.js.map +1 -0
  38. package/dist/utils/slack-operations/channel-operations.d.ts +15 -0
  39. package/dist/utils/slack-operations/channel-operations.d.ts.map +1 -0
  40. package/dist/utils/slack-operations/channel-operations.js +93 -0
  41. package/dist/utils/slack-operations/channel-operations.js.map +1 -0
  42. package/dist/utils/slack-operations/index.d.ts +4 -0
  43. package/dist/utils/slack-operations/index.d.ts.map +1 -0
  44. package/dist/utils/slack-operations/index.js +10 -0
  45. package/dist/utils/slack-operations/index.js.map +1 -0
  46. package/dist/utils/slack-operations/message-operations.d.ts +12 -0
  47. package/dist/utils/slack-operations/message-operations.d.ts.map +1 -0
  48. package/dist/utils/slack-operations/message-operations.js +80 -0
  49. package/dist/utils/slack-operations/message-operations.js.map +1 -0
  50. package/package.json +1 -1
  51. package/src/commands/channels.ts +4 -22
  52. package/src/commands/config-subcommands.ts +63 -0
  53. package/src/commands/config.ts +15 -69
  54. package/src/commands/history-display.ts +36 -0
  55. package/src/commands/history-validators.ts +46 -0
  56. package/src/commands/history.ts +13 -60
  57. package/src/utils/channel-formatter.ts +9 -53
  58. package/src/utils/formatters/channels-list-formatters.ts +59 -0
  59. package/src/utils/slack-api-client.ts +12 -181
  60. package/src/utils/slack-operations/base-client.ts +30 -0
  61. package/src/utils/slack-operations/channel-operations.ts +112 -0
  62. package/src/utils/slack-operations/index.ts +3 -0
  63. package/src/utils/slack-operations/message-operations.ts +94 -0
@@ -1,7 +1,6 @@
1
- import { WebClient, ChatPostMessageResponse, LogLevel } from '@slack/web-api';
2
- import pLimit from 'p-limit';
3
- import { channelResolver } from './channel-resolver';
4
- import { RATE_LIMIT, DEFAULTS } from './constants';
1
+ import { ChatPostMessageResponse } from '@slack/web-api';
2
+ import { ChannelOperations } from './slack-operations/channel-operations';
3
+ import { MessageOperations } from './slack-operations/message-operations';
5
4
 
6
5
  export interface Channel {
7
6
  id: string;
@@ -36,13 +35,6 @@ export interface Channel {
36
35
  };
37
36
  }
38
37
 
39
- // Extended channel interface with additional properties from API
40
- interface ChannelWithUnreadInfo extends Channel {
41
- unread_count: number;
42
- unread_count_display: number;
43
- last_read?: string;
44
- }
45
-
46
38
  export interface ListChannelsOptions {
47
39
  types: string;
48
40
  exclude_archived: boolean;
@@ -77,193 +69,32 @@ export interface ChannelUnreadResult {
77
69
  }
78
70
 
79
71
  export class SlackApiClient {
80
- private client: WebClient;
81
- private rateLimiter: ReturnType<typeof pLimit>;
72
+ private channelOps: ChannelOperations;
73
+ private messageOps: MessageOperations;
82
74
 
83
75
  constructor(token: string) {
84
- this.client = new WebClient(token, {
85
- retryConfig: {
86
- retries: 0, // Disable automatic retries to handle rate limits manually
87
- },
88
- logLevel: LogLevel.ERROR, // Reduce noise from WebClient logs
89
- });
90
- // Limit concurrent API calls to avoid rate limiting
91
- this.rateLimiter = pLimit(RATE_LIMIT.CONCURRENT_REQUESTS);
76
+ this.channelOps = new ChannelOperations(token);
77
+ this.messageOps = new MessageOperations(token);
92
78
  }
93
79
 
94
80
  async sendMessage(channel: string, text: string): Promise<ChatPostMessageResponse> {
95
- return await this.client.chat.postMessage({
96
- channel,
97
- text,
98
- });
81
+ return this.messageOps.sendMessage(channel, text);
99
82
  }
100
83
 
101
84
  async listChannels(options: ListChannelsOptions): Promise<Channel[]> {
102
- const channels: Channel[] = [];
103
- let cursor: string | undefined;
104
-
105
- // Paginate through all channels
106
- do {
107
- const response = await this.client.conversations.list({
108
- types: options.types,
109
- exclude_archived: options.exclude_archived,
110
- limit: options.limit,
111
- cursor,
112
- });
113
-
114
- if (response.channels) {
115
- channels.push(...(response.channels as Channel[]));
116
- }
117
-
118
- cursor = response.response_metadata?.next_cursor;
119
- } while (cursor);
120
-
121
- return channels;
85
+ return this.channelOps.listChannels(options);
122
86
  }
123
87
 
124
88
  async getHistory(channel: string, options: HistoryOptions): Promise<HistoryResult> {
125
- // Resolve channel name to ID if needed
126
- const channelId = await channelResolver.resolveChannelId(channel, () =>
127
- this.listChannels({
128
- types: 'public_channel,private_channel,im,mpim',
129
- exclude_archived: true,
130
- limit: DEFAULTS.CHANNELS_LIMIT,
131
- })
132
- );
133
-
134
- const response = await this.client.conversations.history({
135
- channel: channelId,
136
- limit: options.limit,
137
- oldest: options.oldest,
138
- });
139
-
140
- const messages = response.messages as Message[];
141
-
142
- // Get unique user IDs
143
- const userIds = [...new Set(messages.filter((m) => m.user).map((m) => m.user!))];
144
- const users = new Map<string, string>();
145
-
146
- // Fetch user information
147
- if (userIds.length > 0) {
148
- for (const userId of userIds) {
149
- try {
150
- const userInfo = await this.client.users.info({ user: userId });
151
- if (userInfo.user?.name) {
152
- users.set(userId, userInfo.user.name);
153
- }
154
- } catch (error) {
155
- // If we can't get user info, we'll use the ID
156
- users.set(userId, userId);
157
- }
158
- }
159
- }
160
-
161
- return { messages, users };
89
+ return this.messageOps.getHistory(channel, options);
162
90
  }
163
91
 
164
92
  async listUnreadChannels(): Promise<Channel[]> {
165
- try {
166
- // Use users.conversations to get unread counts in a single API call
167
- const response = await this.client.users.conversations({
168
- types: 'public_channel,private_channel,im,mpim',
169
- exclude_archived: true,
170
- limit: 1000,
171
- user: undefined, // Current authenticated user
172
- });
173
-
174
- const channels = response.channels as Channel[];
175
-
176
- // Filter to only channels with unread messages
177
- // The users.conversations endpoint includes unread_count_display
178
- return channels.filter((channel) => (channel.unread_count_display || 0) > 0);
179
- } catch (error) {
180
- // Fallback to the old method if users.conversations fails
181
- console.warn('Failed to use users.conversations, falling back to conversations.list');
182
- return this.listUnreadChannelsFallback();
183
- }
184
- }
185
-
186
- private async listUnreadChannelsFallback(): Promise<Channel[]> {
187
- // Get all conversations the user is a member of
188
- const response = await this.client.conversations.list({
189
- types: 'public_channel,private_channel,im,mpim',
190
- exclude_archived: true,
191
- limit: 1000,
192
- });
193
-
194
- const channels = response.channels as Channel[];
195
- const channelsWithUnread: Channel[] = [];
196
-
197
- // Process channels one by one with delay to avoid rate limits
198
- for (const channel of channels) {
199
- try {
200
- const info = await this.client.conversations.info({
201
- channel: channel.id,
202
- include_num_members: false,
203
- });
204
- const channelInfo = info.channel as ChannelWithUnreadInfo;
205
-
206
- if (channelInfo.unread_count_display && channelInfo.unread_count_display > 0) {
207
- channelsWithUnread.push({
208
- ...channel,
209
- unread_count: channelInfo.unread_count || 0,
210
- unread_count_display: channelInfo.unread_count_display || 0,
211
- last_read: channelInfo.last_read,
212
- });
213
- }
214
-
215
- // Add delay between API calls to avoid rate limiting
216
- await new Promise((resolve) => setTimeout(resolve, 100));
217
- } catch (error) {
218
- // Skip channels that fail
219
- if (error instanceof Error && error.message?.includes('rate limit')) {
220
- // If we hit rate limit, wait longer
221
- await new Promise((resolve) => setTimeout(resolve, 5000));
222
- }
223
- }
224
- }
225
-
226
- return channelsWithUnread;
93
+ return this.channelOps.listUnreadChannels();
227
94
  }
228
95
 
229
96
  async getChannelUnread(channelNameOrId: string): Promise<ChannelUnreadResult> {
230
- // Resolve channel name to ID if needed
231
- const channelId = await channelResolver.resolveChannelId(channelNameOrId, () =>
232
- this.listChannels({
233
- types: 'public_channel,private_channel,im,mpim',
234
- exclude_archived: true,
235
- limit: DEFAULTS.CHANNELS_LIMIT,
236
- })
237
- );
238
-
239
- // Get channel info with unread count
240
- const info = await this.client.conversations.info({
241
- channel: channelId,
242
- });
243
- const channel = info.channel as ChannelWithUnreadInfo;
244
-
245
- // Get unread messages
246
- let messages: Message[] = [];
247
- let users = new Map<string, string>();
248
-
249
- if (channel.last_read && channel.unread_count > 0) {
250
- const historyResult = await this.getHistory(channelId, {
251
- limit: channel.unread_count,
252
- oldest: channel.last_read,
253
- });
254
- messages = historyResult.messages;
255
- users = historyResult.users;
256
- }
257
-
258
- return {
259
- channel: {
260
- ...channel,
261
- unread_count: channel.unread_count || 0,
262
- unread_count_display: channel.unread_count_display || 0,
263
- },
264
- messages,
265
- users,
266
- };
97
+ return this.messageOps.getChannelUnread(channelNameOrId);
267
98
  }
268
99
  }
269
100
 
@@ -0,0 +1,30 @@
1
+ import { WebClient, LogLevel } from '@slack/web-api';
2
+ import pLimit from 'p-limit';
3
+ import { RATE_LIMIT } from '../constants';
4
+
5
+ export class BaseSlackClient {
6
+ protected client: WebClient;
7
+ protected rateLimiter: ReturnType<typeof pLimit>;
8
+
9
+ constructor(token: string) {
10
+ this.client = new WebClient(token, {
11
+ retryConfig: {
12
+ retries: 0, // Disable automatic retries to handle rate limits manually
13
+ },
14
+ logLevel: LogLevel.ERROR, // Reduce noise from WebClient logs
15
+ });
16
+ // Limit concurrent API calls to avoid rate limiting
17
+ this.rateLimiter = pLimit(RATE_LIMIT.CONCURRENT_REQUESTS);
18
+ }
19
+
20
+ protected async handleRateLimit(error: unknown): Promise<void> {
21
+ if (error instanceof Error && error.message?.includes('rate limit')) {
22
+ // If we hit rate limit, wait longer
23
+ await new Promise((resolve) => setTimeout(resolve, 5000));
24
+ }
25
+ }
26
+
27
+ protected async delay(ms: number): Promise<void> {
28
+ await new Promise((resolve) => setTimeout(resolve, ms));
29
+ }
30
+ }
@@ -0,0 +1,112 @@
1
+ import { BaseSlackClient } from './base-client';
2
+ import { channelResolver } from '../channel-resolver';
3
+ import { DEFAULTS } from '../constants';
4
+ import { Channel, ListChannelsOptions } from '../slack-api-client';
5
+
6
+ interface ChannelWithUnreadInfo extends Channel {
7
+ unread_count: number;
8
+ unread_count_display: number;
9
+ last_read?: string;
10
+ }
11
+
12
+ export class ChannelOperations extends BaseSlackClient {
13
+ async listChannels(options: ListChannelsOptions): Promise<Channel[]> {
14
+ const channels: Channel[] = [];
15
+ let cursor: string | undefined;
16
+
17
+ // Paginate through all channels
18
+ do {
19
+ const response = await this.client.conversations.list({
20
+ types: options.types,
21
+ exclude_archived: options.exclude_archived,
22
+ limit: options.limit,
23
+ cursor,
24
+ });
25
+
26
+ if (response.channels) {
27
+ channels.push(...(response.channels as Channel[]));
28
+ }
29
+
30
+ cursor = response.response_metadata?.next_cursor;
31
+ } while (cursor);
32
+
33
+ return channels;
34
+ }
35
+
36
+ async listUnreadChannels(): Promise<Channel[]> {
37
+ try {
38
+ // Use users.conversations to get unread counts in a single API call
39
+ const response = await this.client.users.conversations({
40
+ types: 'public_channel,private_channel,im,mpim',
41
+ exclude_archived: true,
42
+ limit: 1000,
43
+ });
44
+
45
+ const channels = response.channels as Channel[];
46
+
47
+ // Filter to only channels with unread messages
48
+ // The users.conversations endpoint includes unread_count_display
49
+ return channels.filter((channel) => (channel.unread_count_display || 0) > 0);
50
+ } catch (error) {
51
+ // Fallback to the old method if users.conversations fails
52
+ console.warn('Failed to use users.conversations, falling back to conversations.list');
53
+ return this.listUnreadChannelsFallback();
54
+ }
55
+ }
56
+
57
+ private async listUnreadChannelsFallback(): Promise<Channel[]> {
58
+ // Get all conversations the user is a member of
59
+ const response = await this.client.conversations.list({
60
+ types: 'public_channel,private_channel,im,mpim',
61
+ exclude_archived: true,
62
+ limit: 1000,
63
+ });
64
+
65
+ const channels = response.channels as Channel[];
66
+ const channelsWithUnread: Channel[] = [];
67
+
68
+ // Process channels one by one with delay to avoid rate limits
69
+ for (const channel of channels) {
70
+ try {
71
+ const info = await this.client.conversations.info({
72
+ channel: channel.id,
73
+ include_num_members: false,
74
+ });
75
+ const channelInfo = info.channel as ChannelWithUnreadInfo;
76
+
77
+ if (channelInfo.unread_count_display && channelInfo.unread_count_display > 0) {
78
+ channelsWithUnread.push({
79
+ ...channel,
80
+ unread_count: channelInfo.unread_count || 0,
81
+ unread_count_display: channelInfo.unread_count_display || 0,
82
+ last_read: channelInfo.last_read,
83
+ });
84
+ }
85
+
86
+ // Add delay between API calls to avoid rate limiting
87
+ await this.delay(100);
88
+ } catch (error) {
89
+ // Skip channels that fail
90
+ await this.handleRateLimit(error);
91
+ }
92
+ }
93
+
94
+ return channelsWithUnread;
95
+ }
96
+
97
+ async getChannelInfo(channelNameOrId: string): Promise<ChannelWithUnreadInfo> {
98
+ const channelId = await channelResolver.resolveChannelId(channelNameOrId, () =>
99
+ this.listChannels({
100
+ types: 'public_channel,private_channel,im,mpim',
101
+ exclude_archived: true,
102
+ limit: DEFAULTS.CHANNELS_LIMIT,
103
+ })
104
+ );
105
+
106
+ const info = await this.client.conversations.info({
107
+ channel: channelId,
108
+ });
109
+
110
+ return info.channel as ChannelWithUnreadInfo;
111
+ }
112
+ }
@@ -0,0 +1,3 @@
1
+ export { BaseSlackClient } from './base-client';
2
+ export { ChannelOperations } from './channel-operations';
3
+ export { MessageOperations } from './message-operations';
@@ -0,0 +1,94 @@
1
+ import { ChatPostMessageResponse } from '@slack/web-api';
2
+ import { BaseSlackClient } from './base-client';
3
+ import { channelResolver } from '../channel-resolver';
4
+ import { DEFAULTS } from '../constants';
5
+ import { Message, HistoryOptions, HistoryResult, ChannelUnreadResult } from '../slack-api-client';
6
+ import { ChannelOperations } from './channel-operations';
7
+
8
+ export class MessageOperations extends BaseSlackClient {
9
+ private channelOps: ChannelOperations;
10
+
11
+ constructor(token: string) {
12
+ super(token);
13
+ this.channelOps = new ChannelOperations(token);
14
+ }
15
+
16
+ async sendMessage(channel: string, text: string): Promise<ChatPostMessageResponse> {
17
+ return await this.client.chat.postMessage({
18
+ channel,
19
+ text,
20
+ });
21
+ }
22
+
23
+ async getHistory(channel: string, options: HistoryOptions): Promise<HistoryResult> {
24
+ // Resolve channel name to ID if needed
25
+ const channelId = await channelResolver.resolveChannelId(channel, () =>
26
+ this.channelOps.listChannels({
27
+ types: 'public_channel,private_channel,im,mpim',
28
+ exclude_archived: true,
29
+ limit: DEFAULTS.CHANNELS_LIMIT,
30
+ })
31
+ );
32
+
33
+ const response = await this.client.conversations.history({
34
+ channel: channelId,
35
+ limit: options.limit,
36
+ oldest: options.oldest,
37
+ });
38
+
39
+ const messages = response.messages as Message[];
40
+
41
+ // Get unique user IDs
42
+ const userIds = [...new Set(messages.filter((m) => m.user).map((m) => m.user!))];
43
+ const users = await this.fetchUserInfo(userIds);
44
+
45
+ return { messages, users };
46
+ }
47
+
48
+ async getChannelUnread(channelNameOrId: string): Promise<ChannelUnreadResult> {
49
+ const channel = await this.channelOps.getChannelInfo(channelNameOrId);
50
+
51
+ // Get unread messages
52
+ let messages: Message[] = [];
53
+ let users = new Map<string, string>();
54
+
55
+ if (channel.last_read && channel.unread_count > 0) {
56
+ const historyResult = await this.getHistory(channel.id, {
57
+ limit: channel.unread_count,
58
+ oldest: channel.last_read,
59
+ });
60
+ messages = historyResult.messages;
61
+ users = historyResult.users;
62
+ }
63
+
64
+ return {
65
+ channel: {
66
+ ...channel,
67
+ unread_count: channel.unread_count || 0,
68
+ unread_count_display: channel.unread_count_display || 0,
69
+ },
70
+ messages,
71
+ users,
72
+ };
73
+ }
74
+
75
+ private async fetchUserInfo(userIds: string[]): Promise<Map<string, string>> {
76
+ const users = new Map<string, string>();
77
+
78
+ if (userIds.length > 0) {
79
+ for (const userId of userIds) {
80
+ try {
81
+ const userInfo = await this.client.users.info({ user: userId });
82
+ if (userInfo.user?.name) {
83
+ users.set(userId, userInfo.user.name);
84
+ }
85
+ } catch (error) {
86
+ // If we can't get user info, we'll use the ID
87
+ users.set(userId, userId);
88
+ }
89
+ }
90
+ }
91
+
92
+ return users;
93
+ }
94
+ }