@urugus/slack-cli 0.1.6 → 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 (65) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/commands/channels.d.ts.map +1 -1
  3. package/dist/commands/channels.js +3 -12
  4. package/dist/commands/channels.js.map +1 -1
  5. package/dist/commands/config-subcommands.d.ts +14 -0
  6. package/dist/commands/config-subcommands.d.ts.map +1 -0
  7. package/dist/commands/config-subcommands.js +65 -0
  8. package/dist/commands/config-subcommands.js.map +1 -0
  9. package/dist/commands/config.d.ts.map +1 -1
  10. package/dist/commands/config.js +7 -55
  11. package/dist/commands/config.js.map +1 -1
  12. package/dist/commands/history-display.d.ts +3 -0
  13. package/dist/commands/history-display.d.ts.map +1 -0
  14. package/dist/commands/history-display.js +33 -0
  15. package/dist/commands/history-display.js.map +1 -0
  16. package/dist/commands/history-validators.d.ts +5 -0
  17. package/dist/commands/history-validators.d.ts.map +1 -0
  18. package/dist/commands/history-validators.js +35 -0
  19. package/dist/commands/history-validators.js.map +1 -0
  20. package/dist/commands/history.d.ts.map +1 -1
  21. package/dist/commands/history.js +8 -51
  22. package/dist/commands/history.js.map +1 -1
  23. package/dist/utils/channel-formatter.d.ts +0 -3
  24. package/dist/utils/channel-formatter.d.ts.map +1 -1
  25. package/dist/utils/channel-formatter.js +8 -44
  26. package/dist/utils/channel-formatter.js.map +1 -1
  27. package/dist/utils/formatters/channels-list-formatters.d.ts +13 -0
  28. package/dist/utils/formatters/channels-list-formatters.d.ts.map +1 -0
  29. package/dist/utils/formatters/channels-list-formatters.js +53 -0
  30. package/dist/utils/formatters/channels-list-formatters.js.map +1 -0
  31. package/dist/utils/slack-api-client.d.ts +2 -2
  32. package/dist/utils/slack-api-client.d.ts.map +1 -1
  33. package/dist/utils/slack-api-client.js +9 -140
  34. package/dist/utils/slack-api-client.js.map +1 -1
  35. package/dist/utils/slack-operations/base-client.d.ts +10 -0
  36. package/dist/utils/slack-operations/base-client.d.ts.map +1 -0
  37. package/dist/utils/slack-operations/base-client.js +32 -0
  38. package/dist/utils/slack-operations/base-client.js.map +1 -0
  39. package/dist/utils/slack-operations/channel-operations.d.ts +15 -0
  40. package/dist/utils/slack-operations/channel-operations.d.ts.map +1 -0
  41. package/dist/utils/slack-operations/channel-operations.js +93 -0
  42. package/dist/utils/slack-operations/channel-operations.js.map +1 -0
  43. package/dist/utils/slack-operations/index.d.ts +4 -0
  44. package/dist/utils/slack-operations/index.d.ts.map +1 -0
  45. package/dist/utils/slack-operations/index.js +10 -0
  46. package/dist/utils/slack-operations/index.js.map +1 -0
  47. package/dist/utils/slack-operations/message-operations.d.ts +12 -0
  48. package/dist/utils/slack-operations/message-operations.d.ts.map +1 -0
  49. package/dist/utils/slack-operations/message-operations.js +80 -0
  50. package/dist/utils/slack-operations/message-operations.js.map +1 -0
  51. package/package.json +1 -1
  52. package/src/commands/channels.ts +4 -22
  53. package/src/commands/config-subcommands.ts +63 -0
  54. package/src/commands/config.ts +15 -69
  55. package/src/commands/history-display.ts +36 -0
  56. package/src/commands/history-validators.ts +46 -0
  57. package/src/commands/history.ts +13 -60
  58. package/src/utils/channel-formatter.ts +9 -53
  59. package/src/utils/formatters/channels-list-formatters.ts +59 -0
  60. package/src/utils/slack-api-client.ts +12 -172
  61. package/src/utils/slack-operations/base-client.ts +30 -0
  62. package/src/utils/slack-operations/channel-operations.ts +112 -0
  63. package/src/utils/slack-operations/index.ts +3 -0
  64. package/src/utils/slack-operations/message-operations.ts +94 -0
  65. package/tests/utils/slack-api-client.test.ts +8 -6
@@ -0,0 +1,63 @@
1
+ import chalk from 'chalk';
2
+ import { ProfileConfigManager } from '../utils/profile-config';
3
+ import { getProfileName } from '../utils/command-wrapper';
4
+ import { ERROR_MESSAGES, SUCCESS_MESSAGES } from '../utils/constants';
5
+
6
+ export async function handleSetToken(options: { token: string; profile?: string }): Promise<void> {
7
+ const configManager = new ProfileConfigManager();
8
+ const profileName = await getProfileName(configManager, options.profile);
9
+ await configManager.setToken(options.token, options.profile);
10
+ console.log(chalk.green(`✓ ${SUCCESS_MESSAGES.TOKEN_SAVED(profileName)}`));
11
+ }
12
+
13
+ export async function handleGetConfig(options: { profile?: string }): Promise<void> {
14
+ const configManager = new ProfileConfigManager();
15
+ const profileName = await getProfileName(configManager, options.profile);
16
+ const currentConfig = await configManager.getConfig(options.profile);
17
+
18
+ if (!currentConfig) {
19
+ console.log(chalk.yellow(ERROR_MESSAGES.NO_CONFIG(profileName)));
20
+ return;
21
+ }
22
+
23
+ console.log(chalk.bold(`Configuration for profile "${profileName}":`));
24
+ console.log(` Token: ${chalk.cyan(configManager.maskToken(currentConfig.token))}`);
25
+ console.log(` Updated: ${chalk.gray(currentConfig.updatedAt)}`);
26
+ }
27
+
28
+ export async function handleListProfiles(): Promise<void> {
29
+ const configManager = new ProfileConfigManager();
30
+ const profiles = await configManager.listProfiles();
31
+ const currentProfile = await configManager.getCurrentProfile();
32
+
33
+ if (profiles.length === 0) {
34
+ console.log(chalk.yellow(ERROR_MESSAGES.NO_PROFILES_FOUND));
35
+ return;
36
+ }
37
+
38
+ console.log(chalk.bold('Available profiles:'));
39
+ profiles.forEach((profile) => {
40
+ const marker = profile.name === currentProfile ? '*' : ' ';
41
+ const maskedToken = configManager.maskToken(profile.config.token);
42
+ console.log(` ${marker} ${chalk.cyan(profile.name)} (${maskedToken})`);
43
+ });
44
+ }
45
+
46
+ export async function handleUseProfile(profile: string): Promise<void> {
47
+ const configManager = new ProfileConfigManager();
48
+ await configManager.useProfile(profile);
49
+ console.log(chalk.green(`✓ ${SUCCESS_MESSAGES.PROFILE_SWITCHED(profile)}`));
50
+ }
51
+
52
+ export async function handleShowCurrentProfile(): Promise<void> {
53
+ const configManager = new ProfileConfigManager();
54
+ const currentProfile = await configManager.getCurrentProfile();
55
+ console.log(chalk.bold(`Current profile: ${chalk.cyan(currentProfile)}`));
56
+ }
57
+
58
+ export async function handleClearConfig(options: { profile?: string }): Promise<void> {
59
+ const configManager = new ProfileConfigManager();
60
+ const profileName = await getProfileName(configManager, options.profile);
61
+ await configManager.clearConfig(options.profile);
62
+ console.log(chalk.green(`✓ ${SUCCESS_MESSAGES.PROFILE_CLEARED(profileName)}`));
63
+ }
@@ -1,8 +1,13 @@
1
1
  import { Command } from 'commander';
2
- import chalk from 'chalk';
3
- import { ProfileConfigManager } from '../utils/profile-config';
4
- import { wrapCommand, getProfileName } from '../utils/command-wrapper';
5
- import { ERROR_MESSAGES, SUCCESS_MESSAGES } from '../utils/constants';
2
+ import { wrapCommand } from '../utils/command-wrapper';
3
+ import {
4
+ handleSetToken,
5
+ handleGetConfig,
6
+ handleListProfiles,
7
+ handleUseProfile,
8
+ handleShowCurrentProfile,
9
+ handleClearConfig,
10
+ } from './config-subcommands';
6
11
 
7
12
  export function setupConfigCommand(): Command {
8
13
  const config = new Command('config').description('Manage Slack CLI configuration');
@@ -12,93 +17,34 @@ export function setupConfigCommand(): Command {
12
17
  .description('Set API token')
13
18
  .requiredOption('--token <token>', 'Slack API token')
14
19
  .option('--profile <profile>', 'Profile name (default: "default")')
15
- .action(
16
- wrapCommand(async (options) => {
17
- const configManager = new ProfileConfigManager();
18
- const profileName = await getProfileName(configManager, options.profile);
19
- await configManager.setToken(options.token, options.profile);
20
- console.log(chalk.green(`✓ ${SUCCESS_MESSAGES.TOKEN_SAVED(profileName)}`));
21
- })
22
- );
20
+ .action(wrapCommand(handleSetToken));
23
21
 
24
22
  config
25
23
  .command('get')
26
24
  .description('Show current configuration')
27
25
  .option('--profile <profile>', 'Profile name')
28
- .action(
29
- wrapCommand(async (options) => {
30
- const configManager = new ProfileConfigManager();
31
- const profileName = await getProfileName(configManager, options.profile);
32
- const currentConfig = await configManager.getConfig(options.profile);
33
-
34
- if (!currentConfig) {
35
- console.log(chalk.yellow(ERROR_MESSAGES.NO_CONFIG(profileName)));
36
- return;
37
- }
38
-
39
- console.log(chalk.bold(`Configuration for profile "${profileName}":`));
40
- console.log(` Token: ${chalk.cyan(configManager.maskToken(currentConfig.token))}`);
41
- console.log(` Updated: ${chalk.gray(currentConfig.updatedAt)}`);
42
- })
43
- );
26
+ .action(wrapCommand(handleGetConfig));
44
27
 
45
28
  config
46
29
  .command('profiles')
47
30
  .description('List all profiles')
48
- .action(
49
- wrapCommand(async () => {
50
- const configManager = new ProfileConfigManager();
51
- const profiles = await configManager.listProfiles();
52
- const currentProfile = await configManager.getCurrentProfile();
53
-
54
- if (profiles.length === 0) {
55
- console.log(chalk.yellow(ERROR_MESSAGES.NO_PROFILES_FOUND));
56
- return;
57
- }
58
-
59
- console.log(chalk.bold('Available profiles:'));
60
- profiles.forEach((profile) => {
61
- const marker = profile.name === currentProfile ? '*' : ' ';
62
- const maskedToken = configManager.maskToken(profile.config.token);
63
- console.log(` ${marker} ${chalk.cyan(profile.name)} (${maskedToken})`);
64
- });
65
- })
66
- );
31
+ .action(wrapCommand(handleListProfiles));
67
32
 
68
33
  config
69
34
  .command('use <profile>')
70
35
  .description('Switch to a different profile')
71
- .action(
72
- wrapCommand(async (profile) => {
73
- const configManager = new ProfileConfigManager();
74
- await configManager.useProfile(profile);
75
- console.log(chalk.green(`✓ ${SUCCESS_MESSAGES.PROFILE_SWITCHED(profile)}`));
76
- })
77
- );
36
+ .action(wrapCommand(handleUseProfile));
78
37
 
79
38
  config
80
39
  .command('current')
81
40
  .description('Show current active profile')
82
- .action(
83
- wrapCommand(async () => {
84
- const configManager = new ProfileConfigManager();
85
- const currentProfile = await configManager.getCurrentProfile();
86
- console.log(chalk.bold(`Current profile: ${chalk.cyan(currentProfile)}`));
87
- })
88
- );
41
+ .action(wrapCommand(handleShowCurrentProfile));
89
42
 
90
43
  config
91
44
  .command('clear')
92
45
  .description('Clear configuration')
93
46
  .option('--profile <profile>', 'Profile name')
94
- .action(
95
- wrapCommand(async (options) => {
96
- const configManager = new ProfileConfigManager();
97
- const profileName = await getProfileName(configManager, options.profile);
98
- await configManager.clearConfig(options.profile);
99
- console.log(chalk.green(`✓ ${SUCCESS_MESSAGES.PROFILE_CLEARED(profileName)}`));
100
- })
101
- );
47
+ .action(wrapCommand(handleClearConfig));
102
48
 
103
49
  return config;
104
50
  }
@@ -0,0 +1,36 @@
1
+ import chalk from 'chalk';
2
+ import { Message } from '../utils/slack-api-client';
3
+ import { formatSlackTimestamp } from '../utils/date-utils';
4
+
5
+ export function displayHistoryResults(
6
+ messages: Message[],
7
+ users: Map<string, string>,
8
+ channelName: string
9
+ ): void {
10
+ if (messages.length === 0) {
11
+ console.log(chalk.yellow('No messages found in the specified channel.'));
12
+ return;
13
+ }
14
+
15
+ console.log(chalk.bold(`\nMessage History for #${channelName}:\n`));
16
+
17
+ // Display messages in reverse order (oldest first)
18
+ messages.reverse().forEach((message: Message) => {
19
+ const timestamp = formatSlackTimestamp(message.ts);
20
+ let author = 'Unknown';
21
+
22
+ if (message.user && users.has(message.user)) {
23
+ author = users.get(message.user)!;
24
+ } else if (message.bot_id) {
25
+ author = 'Bot';
26
+ }
27
+
28
+ console.log(chalk.gray(`[${timestamp}]`) + ' ' + chalk.cyan(author));
29
+ if (message.text) {
30
+ console.log(message.text);
31
+ }
32
+ console.log(''); // Empty line between messages
33
+ });
34
+
35
+ console.log(chalk.green(`✓ Displayed ${messages.length} message(s)`));
36
+ }
@@ -0,0 +1,46 @@
1
+ import { Command } from 'commander';
2
+ import { API_LIMITS } from '../utils/constants';
3
+
4
+ export function validateMessageCount(
5
+ value: string | undefined,
6
+ command: Command
7
+ ): number | undefined {
8
+ if (!value) {
9
+ return undefined;
10
+ }
11
+
12
+ const num = parseInt(value, 10);
13
+ if (isNaN(num) || num < API_LIMITS.MIN_MESSAGE_COUNT || num > API_LIMITS.MAX_MESSAGE_COUNT) {
14
+ command.error(
15
+ `Error: Message count must be between ${API_LIMITS.MIN_MESSAGE_COUNT} and ${API_LIMITS.MAX_MESSAGE_COUNT}`
16
+ );
17
+ }
18
+
19
+ return num;
20
+ }
21
+
22
+ export function validateDateFormat(
23
+ value: string | undefined,
24
+ command: Command
25
+ ): string | undefined {
26
+ if (!value) {
27
+ return undefined;
28
+ }
29
+
30
+ const timestamp = Date.parse(value);
31
+ if (isNaN(timestamp)) {
32
+ command.error('Error: Invalid date format. Use YYYY-MM-DD HH:MM:SS');
33
+ }
34
+
35
+ return value;
36
+ }
37
+
38
+ export function prepareSinceTimestamp(since: string | undefined): string | undefined {
39
+ if (!since) {
40
+ return undefined;
41
+ }
42
+
43
+ // Convert date to Unix timestamp (in seconds)
44
+ const timestamp = Math.floor(Date.parse(since) / 1000);
45
+ return timestamp.toString();
46
+ }
@@ -1,11 +1,15 @@
1
1
  import { Command } from 'commander';
2
- import chalk from 'chalk';
3
- import { HistoryOptions as ApiHistoryOptions, Message } from '../utils/slack-api-client';
2
+ import { HistoryOptions as ApiHistoryOptions } from '../utils/slack-api-client';
4
3
  import { wrapCommand } from '../utils/command-wrapper';
5
4
  import { createSlackClient } from '../utils/client-factory';
6
5
  import { HistoryOptions } from '../types/commands';
7
- import { formatSlackTimestamp } from '../utils/date-utils';
8
6
  import { API_LIMITS } from '../utils/constants';
7
+ import {
8
+ validateMessageCount,
9
+ validateDateFormat,
10
+ prepareSinceTimestamp,
11
+ } from './history-validators';
12
+ import { displayHistoryResults } from './history-display';
9
13
 
10
14
  export function setupHistoryCommand(): Command {
11
15
  const historyCommand = new Command('history')
@@ -20,75 +24,24 @@ export function setupHistoryCommand(): Command {
20
24
  .option('--profile <profile>', 'Use specific workspace profile')
21
25
  .hook('preAction', (thisCommand) => {
22
26
  const options = thisCommand.opts();
23
-
24
- // Validate number option
25
- if (options.number) {
26
- const num = parseInt(options.number, 10);
27
- if (
28
- isNaN(num) ||
29
- num < API_LIMITS.MIN_MESSAGE_COUNT ||
30
- num > API_LIMITS.MAX_MESSAGE_COUNT
31
- ) {
32
- thisCommand.error(
33
- `Error: Message count must be between ${API_LIMITS.MIN_MESSAGE_COUNT} and ${API_LIMITS.MAX_MESSAGE_COUNT}`
34
- );
35
- }
36
- }
37
-
38
- // Validate since option
39
- if (options.since) {
40
- const timestamp = Date.parse(options.since);
41
- if (isNaN(timestamp)) {
42
- thisCommand.error('Error: Invalid date format. Use YYYY-MM-DD HH:MM:SS');
43
- }
44
- }
27
+ validateMessageCount(options.number, thisCommand);
28
+ validateDateFormat(options.since, thisCommand);
45
29
  })
46
30
  .action(
47
31
  wrapCommand(async (options: HistoryOptions) => {
48
- // Create Slack client
49
32
  const client = await createSlackClient(options.profile);
50
33
 
51
- // Prepare API options
52
34
  const historyOptions: ApiHistoryOptions = {
53
35
  limit: parseInt(options.number || API_LIMITS.DEFAULT_MESSAGE_COUNT.toString(), 10),
54
36
  };
55
37
 
56
- if (options.since) {
57
- // Convert date to Unix timestamp (in seconds)
58
- const timestamp = Math.floor(Date.parse(options.since) / 1000);
59
- historyOptions.oldest = timestamp.toString();
38
+ const oldest = prepareSinceTimestamp(options.since);
39
+ if (oldest) {
40
+ historyOptions.oldest = oldest;
60
41
  }
61
42
 
62
- // Get message history
63
43
  const { messages, users } = await client.getHistory(options.channel, historyOptions);
64
-
65
- // Display results
66
- if (messages.length === 0) {
67
- console.log(chalk.yellow('No messages found in the specified channel.'));
68
- return;
69
- }
70
-
71
- console.log(chalk.bold(`\nMessage History for #${options.channel}:\n`));
72
-
73
- // Display messages in reverse order (oldest first)
74
- messages.reverse().forEach((message: Message) => {
75
- const timestamp = formatSlackTimestamp(message.ts);
76
- let author = 'Unknown';
77
-
78
- if (message.user && users.has(message.user)) {
79
- author = users.get(message.user)!;
80
- } else if (message.bot_id) {
81
- author = 'Bot';
82
- }
83
-
84
- console.log(chalk.gray(`[${timestamp}]`) + ' ' + chalk.cyan(author));
85
- if (message.text) {
86
- console.log(message.text);
87
- }
88
- console.log(''); // Empty line between messages
89
- });
90
-
91
- console.log(chalk.green(`✓ Displayed ${messages.length} message(s)`));
44
+ displayHistoryResults(messages, users, options.channel);
92
45
  })
93
46
  );
94
47
 
@@ -27,63 +27,19 @@ export function mapChannelToInfo(channel: Channel): ChannelInfo {
27
27
  };
28
28
  }
29
29
 
30
- export function formatChannelsAsTable(channels: ChannelInfo[]): void {
31
- // Print table header
32
- console.log('Name Type Members Created Description');
33
- console.log('─'.repeat(65));
34
-
35
- // Print channel rows
36
- channels.forEach((channel) => {
37
- const name = channel.name.padEnd(17);
38
- const type = channel.type.padEnd(9);
39
- const members = channel.members.toString().padEnd(8);
40
- const created = channel.created.padEnd(12);
41
- const purpose =
42
- channel.purpose.length > 30 ? channel.purpose.substring(0, 27) + '...' : channel.purpose;
43
-
44
- console.log(`${name} ${type} ${members} ${created} ${purpose}`);
45
- });
46
- }
47
-
48
- export function formatChannelsAsSimple(channels: ChannelInfo[]): void {
49
- channels.forEach((channel) => console.log(channel.name));
50
- }
51
-
52
- export function formatChannelsAsJson(channels: ChannelInfo[]): void {
53
- console.log(
54
- JSON.stringify(
55
- channels.map((channel) => ({
56
- id: channel.id,
57
- name: channel.name,
58
- type: channel.type,
59
- members: channel.members,
60
- created: channel.created + 'T00:00:00Z',
61
- purpose: channel.purpose,
62
- })),
63
- null,
64
- 2
65
- )
66
- );
67
- }
68
-
69
30
  export function formatChannelName(channelName?: string): string {
70
31
  if (!channelName) return '#unknown';
71
32
  return channelName.startsWith('#') ? channelName : `#${channelName}`;
72
33
  }
73
34
 
74
35
  export function getChannelTypes(type: string): string {
75
- switch (type) {
76
- case 'public':
77
- return 'public_channel';
78
- case 'private':
79
- return 'private_channel';
80
- case 'im':
81
- return 'im';
82
- case 'mpim':
83
- return 'mpim';
84
- case 'all':
85
- return 'public_channel,private_channel,mpim,im';
86
- default:
87
- return 'public_channel';
88
- }
36
+ const channelTypeMap: Record<string, string> = {
37
+ public: 'public_channel',
38
+ private: 'private_channel',
39
+ im: 'im',
40
+ mpim: 'mpim',
41
+ all: 'public_channel,private_channel,mpim,im',
42
+ };
43
+
44
+ return channelTypeMap[type] || 'public_channel';
89
45
  }
@@ -0,0 +1,59 @@
1
+ import { BaseFormatter } from './output-formatter';
2
+ import { ChannelInfo } from '../channel-formatter';
3
+
4
+ export class ChannelsTableFormatter extends BaseFormatter<ChannelInfo> {
5
+ format(channels: ChannelInfo[]): void {
6
+ // Print table header
7
+ console.log('Name Type Members Created Description');
8
+ console.log('─'.repeat(65));
9
+
10
+ // Print channel rows
11
+ channels.forEach((channel) => {
12
+ const name = channel.name.padEnd(17);
13
+ const type = channel.type.padEnd(9);
14
+ const members = channel.members.toString().padEnd(8);
15
+ const created = channel.created.padEnd(12);
16
+ const purpose =
17
+ channel.purpose.length > 30 ? channel.purpose.substring(0, 27) + '...' : channel.purpose;
18
+
19
+ console.log(`${name} ${type} ${members} ${created} ${purpose}`);
20
+ });
21
+ }
22
+ }
23
+
24
+ export class ChannelsSimpleFormatter extends BaseFormatter<ChannelInfo> {
25
+ format(channels: ChannelInfo[]): void {
26
+ channels.forEach((channel) => console.log(channel.name));
27
+ }
28
+ }
29
+
30
+ export class ChannelsJsonFormatter extends BaseFormatter<ChannelInfo> {
31
+ format(channels: ChannelInfo[]): void {
32
+ console.log(
33
+ JSON.stringify(
34
+ channels.map((channel) => ({
35
+ id: channel.id,
36
+ name: channel.name,
37
+ type: channel.type,
38
+ members: channel.members,
39
+ created: channel.created + 'T00:00:00Z',
40
+ purpose: channel.purpose,
41
+ })),
42
+ null,
43
+ 2
44
+ )
45
+ );
46
+ }
47
+ }
48
+
49
+ export function createChannelsListFormatter(format: string): BaseFormatter<ChannelInfo> {
50
+ switch (format) {
51
+ case 'json':
52
+ return new ChannelsJsonFormatter();
53
+ case 'simple':
54
+ return new ChannelsSimpleFormatter();
55
+ case 'table':
56
+ default:
57
+ return new ChannelsTableFormatter();
58
+ }
59
+ }