@urugus/slack-cli 0.2.12 → 0.2.13
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/README.md +4 -0
- package/dist/commands/history-display.d.ts +5 -1
- package/dist/commands/history-display.d.ts.map +1 -1
- package/dist/commands/history-display.js +3 -3
- package/dist/commands/history-display.js.map +1 -1
- package/dist/commands/history.d.ts.map +1 -1
- package/dist/commands/history.js +28 -11
- package/dist/commands/history.js.map +1 -1
- package/dist/types/commands.d.ts +1 -0
- package/dist/types/commands.d.ts.map +1 -1
- package/dist/utils/slack-api-client.d.ts +1 -0
- package/dist/utils/slack-api-client.d.ts.map +1 -1
- package/dist/utils/slack-api-client.js +3 -0
- package/dist/utils/slack-api-client.js.map +1 -1
- package/dist/utils/slack-operations/message-operations.d.ts +1 -0
- package/dist/utils/slack-operations/message-operations.d.ts.map +1 -1
- package/dist/utils/slack-operations/message-operations.js +21 -0
- package/dist/utils/slack-operations/message-operations.js.map +1 -1
- package/package.json +5 -2
- package/.claude/settings.local.json +0 -75
- package/.github/dependabot.yml +0 -18
- package/.github/workflows/ci.yml +0 -70
- package/.github/workflows/pr-validation.yml +0 -41
- package/.prettierignore +0 -11
- package/.prettierrc +0 -10
- package/CHANGELOG.md +0 -61
- package/CLAUDE.md +0 -16
- package/eslint.config.js +0 -38
- package/src/commands/channels.ts +0 -50
- package/src/commands/config-subcommands.ts +0 -63
- package/src/commands/config.ts +0 -50
- package/src/commands/history-display.ts +0 -19
- package/src/commands/history-validators.ts +0 -46
- package/src/commands/history.ts +0 -61
- package/src/commands/scheduled.ts +0 -71
- package/src/commands/send.ts +0 -69
- package/src/commands/unread.ts +0 -122
- package/src/index.ts +0 -27
- package/src/types/commands.ts +0 -58
- package/src/types/config.ts +0 -20
- package/src/utils/channel-formatter.ts +0 -45
- package/src/utils/channel-resolver.ts +0 -82
- package/src/utils/client-factory.ts +0 -10
- package/src/utils/command-wrapper.ts +0 -27
- package/src/utils/config/config-file-manager.ts +0 -56
- package/src/utils/config/profile-manager.ts +0 -79
- package/src/utils/config/token-crypto-service.ts +0 -80
- package/src/utils/config-helper.ts +0 -21
- package/src/utils/constants.ts +0 -78
- package/src/utils/date-utils.ts +0 -8
- package/src/utils/error-utils.ts +0 -6
- package/src/utils/errors.ts +0 -33
- package/src/utils/format-utils.ts +0 -9
- package/src/utils/formatters/base-formatter.ts +0 -34
- package/src/utils/formatters/channel-formatters.ts +0 -71
- package/src/utils/formatters/channels-list-formatters.ts +0 -55
- package/src/utils/formatters/history-formatters.ts +0 -123
- package/src/utils/formatters/message-formatters.ts +0 -85
- package/src/utils/mention-utils.ts +0 -47
- package/src/utils/option-parsers.ts +0 -100
- package/src/utils/profile-config.ts +0 -161
- package/src/utils/schedule-utils.ts +0 -41
- package/src/utils/slack-api-client.ts +0 -135
- package/src/utils/slack-operations/base-client.ts +0 -30
- package/src/utils/slack-operations/channel-operations.ts +0 -161
- package/src/utils/slack-operations/index.ts +0 -3
- package/src/utils/slack-operations/message-operations.ts +0 -176
- package/src/utils/slack-patterns.ts +0 -9
- package/src/utils/token-utils.ts +0 -17
- package/src/utils/validators.ts +0 -263
- package/tests/commands/channels.test.ts +0 -250
- package/tests/commands/config.test.ts +0 -158
- package/tests/commands/history.test.ts +0 -403
- package/tests/commands/scheduled.test.ts +0 -131
- package/tests/commands/send.test.ts +0 -414
- package/tests/commands/unread.test.ts +0 -492
- package/tests/index.test.ts +0 -40
- package/tests/test-utils.ts +0 -28
- package/tests/utils/channel-resolver.test.ts +0 -161
- package/tests/utils/config/config-file-manager.test.ts +0 -118
- package/tests/utils/config/profile-manager.test.ts +0 -266
- package/tests/utils/config/token-crypto-service.test.ts +0 -98
- package/tests/utils/config.test.ts +0 -400
- package/tests/utils/date-utils.test.ts +0 -30
- package/tests/utils/error-utils.test.ts +0 -34
- package/tests/utils/format-utils.test.ts +0 -61
- package/tests/utils/mention-utils.test.ts +0 -100
- package/tests/utils/option-parsers.test.ts +0 -173
- package/tests/utils/profile-config.test.ts +0 -282
- package/tests/utils/schedule-utils.test.ts +0 -63
- package/tests/utils/slack-api-client.test.ts +0 -313
- package/tests/utils/slack-operations/channel-operations.test.ts +0 -248
- package/tests/utils/slack-operations/message-operations.test.ts +0 -163
- package/tests/utils/token-utils.test.ts +0 -33
- package/tests/utils/validators.test.ts +0 -307
- package/tsconfig.json +0 -22
- package/vitest.config.ts +0 -27
package/src/commands/channels.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { wrapCommand } from '../utils/command-wrapper';
|
|
3
|
-
import { createSlackClient } from '../utils/client-factory';
|
|
4
|
-
import { ERROR_MESSAGES } from '../utils/constants';
|
|
5
|
-
import { ChannelsOptions } from '../types/commands';
|
|
6
|
-
import { mapChannelToInfo, getChannelTypes } from '../utils/channel-formatter';
|
|
7
|
-
import { createChannelsListFormatter } from '../utils/formatters/channels-list-formatters';
|
|
8
|
-
import { parseFormat, parseLimit, parseBoolean } from '../utils/option-parsers';
|
|
9
|
-
|
|
10
|
-
export function setupChannelsCommand(): Command {
|
|
11
|
-
const channelsCommand = new Command('channels');
|
|
12
|
-
|
|
13
|
-
channelsCommand
|
|
14
|
-
.description('List Slack channels')
|
|
15
|
-
.option('--type <type>', 'Channel type: public, private, im, mpim, all', 'public')
|
|
16
|
-
.option('--include-archived', 'Include archived channels', false)
|
|
17
|
-
.option('--format <format>', 'Output format: table, simple, json', 'table')
|
|
18
|
-
.option('--limit <number>', 'Maximum number of channels to list', '100')
|
|
19
|
-
.option('--profile <profile>', 'Use specific workspace profile')
|
|
20
|
-
.action(
|
|
21
|
-
wrapCommand(async (options: ChannelsOptions) => {
|
|
22
|
-
// Create Slack client
|
|
23
|
-
const client = await createSlackClient(options.profile);
|
|
24
|
-
|
|
25
|
-
// Map channel type to API types
|
|
26
|
-
const types = getChannelTypes(options.type);
|
|
27
|
-
|
|
28
|
-
// List channels
|
|
29
|
-
const limit = parseLimit(options.limit, 100);
|
|
30
|
-
const channels = await client.listChannels({
|
|
31
|
-
types,
|
|
32
|
-
exclude_archived: !parseBoolean(options.includeArchived),
|
|
33
|
-
limit: limit,
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
if (channels.length === 0) {
|
|
37
|
-
console.log(ERROR_MESSAGES.NO_CHANNELS_FOUND);
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Format and display channels
|
|
42
|
-
const channelInfos = channels.map(mapChannelToInfo);
|
|
43
|
-
const format = parseFormat(options.format);
|
|
44
|
-
const formatter = createChannelsListFormatter(format);
|
|
45
|
-
formatter.format({ channels: channelInfos });
|
|
46
|
-
})
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
return channelsCommand;
|
|
50
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
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
|
-
}
|
package/src/commands/config.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
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';
|
|
11
|
-
|
|
12
|
-
export function setupConfigCommand(): Command {
|
|
13
|
-
const config = new Command('config').description('Manage Slack CLI configuration');
|
|
14
|
-
|
|
15
|
-
config
|
|
16
|
-
.command('set')
|
|
17
|
-
.description('Set API token')
|
|
18
|
-
.requiredOption('--token <token>', 'Slack API token')
|
|
19
|
-
.option('--profile <profile>', 'Profile name (default: "default")')
|
|
20
|
-
.action(wrapCommand(handleSetToken));
|
|
21
|
-
|
|
22
|
-
config
|
|
23
|
-
.command('get')
|
|
24
|
-
.description('Show current configuration')
|
|
25
|
-
.option('--profile <profile>', 'Profile name')
|
|
26
|
-
.action(wrapCommand(handleGetConfig));
|
|
27
|
-
|
|
28
|
-
config
|
|
29
|
-
.command('profiles')
|
|
30
|
-
.description('List all profiles')
|
|
31
|
-
.action(wrapCommand(handleListProfiles));
|
|
32
|
-
|
|
33
|
-
config
|
|
34
|
-
.command('use <profile>')
|
|
35
|
-
.description('Switch to a different profile')
|
|
36
|
-
.action(wrapCommand(handleUseProfile));
|
|
37
|
-
|
|
38
|
-
config
|
|
39
|
-
.command('current')
|
|
40
|
-
.description('Show current active profile')
|
|
41
|
-
.action(wrapCommand(handleShowCurrentProfile));
|
|
42
|
-
|
|
43
|
-
config
|
|
44
|
-
.command('clear')
|
|
45
|
-
.description('Clear configuration')
|
|
46
|
-
.option('--profile <profile>', 'Profile name')
|
|
47
|
-
.action(wrapCommand(handleClearConfig));
|
|
48
|
-
|
|
49
|
-
return config;
|
|
50
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { Message } from '../utils/slack-api-client';
|
|
2
|
-
import { createHistoryFormatter } from '../utils/formatters/history-formatters';
|
|
3
|
-
|
|
4
|
-
export function displayHistoryResults(
|
|
5
|
-
messages: Message[],
|
|
6
|
-
users: Map<string, string>,
|
|
7
|
-
channelName: string,
|
|
8
|
-
format = 'table'
|
|
9
|
-
): void {
|
|
10
|
-
// Display messages in reverse order (oldest first)
|
|
11
|
-
const orderedMessages = [...messages].reverse();
|
|
12
|
-
|
|
13
|
-
const formatter = createHistoryFormatter(format);
|
|
14
|
-
formatter.format({
|
|
15
|
-
channelName,
|
|
16
|
-
messages: orderedMessages,
|
|
17
|
-
users,
|
|
18
|
-
});
|
|
19
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
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
|
-
}
|
package/src/commands/history.ts
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { HistoryOptions as ApiHistoryOptions } from '../utils/slack-api-client';
|
|
3
|
-
import { wrapCommand } from '../utils/command-wrapper';
|
|
4
|
-
import { createSlackClient } from '../utils/client-factory';
|
|
5
|
-
import { HistoryOptions } from '../types/commands';
|
|
6
|
-
import { API_LIMITS } from '../utils/constants';
|
|
7
|
-
import { parseCount, parseProfile } from '../utils/option-parsers';
|
|
8
|
-
import { createValidationHook, optionValidators } from '../utils/validators';
|
|
9
|
-
import { parseFormat } from '../utils/option-parsers';
|
|
10
|
-
import { prepareSinceTimestamp } from './history-validators';
|
|
11
|
-
import { displayHistoryResults } from './history-display';
|
|
12
|
-
|
|
13
|
-
export function setupHistoryCommand(): Command {
|
|
14
|
-
const historyCommand = new Command('history')
|
|
15
|
-
.description('Get message history from a Slack channel')
|
|
16
|
-
.requiredOption('-c, --channel <channel>', 'Target channel name or ID')
|
|
17
|
-
.option(
|
|
18
|
-
'-n, --number <number>',
|
|
19
|
-
'Number of messages to retrieve',
|
|
20
|
-
API_LIMITS.DEFAULT_MESSAGE_COUNT.toString()
|
|
21
|
-
)
|
|
22
|
-
.option('--since <date>', 'Get messages since specific date (YYYY-MM-DD HH:MM:SS)')
|
|
23
|
-
.option('--format <format>', 'Output format: table, simple, json', 'table')
|
|
24
|
-
.option('--profile <profile>', 'Use specific workspace profile')
|
|
25
|
-
.hook(
|
|
26
|
-
'preAction',
|
|
27
|
-
createValidationHook([
|
|
28
|
-
optionValidators.messageCount,
|
|
29
|
-
optionValidators.sinceDate,
|
|
30
|
-
optionValidators.format,
|
|
31
|
-
])
|
|
32
|
-
)
|
|
33
|
-
.action(
|
|
34
|
-
wrapCommand(async (options: HistoryOptions) => {
|
|
35
|
-
const profile = parseProfile(options.profile);
|
|
36
|
-
const client = await createSlackClient(profile);
|
|
37
|
-
|
|
38
|
-
const limit = parseCount(
|
|
39
|
-
options.number,
|
|
40
|
-
API_LIMITS.DEFAULT_MESSAGE_COUNT,
|
|
41
|
-
API_LIMITS.MIN_MESSAGE_COUNT,
|
|
42
|
-
API_LIMITS.MAX_MESSAGE_COUNT
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
const historyOptions: ApiHistoryOptions = {
|
|
46
|
-
limit,
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const oldest = prepareSinceTimestamp(options.since);
|
|
50
|
-
if (oldest) {
|
|
51
|
-
historyOptions.oldest = oldest;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const { messages, users } = await client.getHistory(options.channel, historyOptions);
|
|
55
|
-
const format = parseFormat(options.format);
|
|
56
|
-
displayHistoryResults(messages, users, options.channel, format);
|
|
57
|
-
})
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
return historyCommand;
|
|
61
|
-
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { wrapCommand } from '../utils/command-wrapper';
|
|
3
|
-
import { createSlackClient } from '../utils/client-factory';
|
|
4
|
-
import { ScheduledOptions } from '../types/commands';
|
|
5
|
-
import { parseFormat, parseLimit } from '../utils/option-parsers';
|
|
6
|
-
import { createValidationHook, optionValidators } from '../utils/validators';
|
|
7
|
-
|
|
8
|
-
function formatPostAt(postAt: number): string {
|
|
9
|
-
return new Date(postAt * 1000).toISOString();
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function renderTable(
|
|
13
|
-
messages: Array<{ id: string; channel_id: string; post_at: number; text?: string }>
|
|
14
|
-
) {
|
|
15
|
-
const rows = messages.map((message) => ({
|
|
16
|
-
id: message.id,
|
|
17
|
-
channel: message.channel_id,
|
|
18
|
-
post_at: formatPostAt(message.post_at),
|
|
19
|
-
text: message.text || '',
|
|
20
|
-
}));
|
|
21
|
-
|
|
22
|
-
console.table(rows);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function renderSimple(
|
|
26
|
-
messages: Array<{ id: string; channel_id: string; post_at: number; text?: string }>
|
|
27
|
-
) {
|
|
28
|
-
for (const message of messages) {
|
|
29
|
-
console.log(
|
|
30
|
-
`${formatPostAt(message.post_at)} ${message.channel_id} ${message.id} ${message.text || ''}`
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function setupScheduledCommand(): Command {
|
|
36
|
-
const scheduledCommand = new Command('scheduled')
|
|
37
|
-
.description('List scheduled messages')
|
|
38
|
-
.option('-c, --channel <channel>', 'Filter by channel name or ID')
|
|
39
|
-
.option('--limit <number>', 'Maximum number of scheduled messages to list', '50')
|
|
40
|
-
.option('--format <format>', 'Output format: table, simple, json', 'table')
|
|
41
|
-
.option('--profile <profile>', 'Use specific workspace profile')
|
|
42
|
-
.hook('preAction', createValidationHook([optionValidators.format]))
|
|
43
|
-
.action(
|
|
44
|
-
wrapCommand(async (options: ScheduledOptions) => {
|
|
45
|
-
const client = await createSlackClient(options.profile);
|
|
46
|
-
const limit = parseLimit(options.limit, 50);
|
|
47
|
-
const messages = await client.listScheduledMessages(options.channel, limit);
|
|
48
|
-
|
|
49
|
-
if (messages.length === 0) {
|
|
50
|
-
console.log('No scheduled messages found');
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const format = parseFormat(options.format);
|
|
55
|
-
|
|
56
|
-
if (format === 'json') {
|
|
57
|
-
console.log(JSON.stringify(messages, null, 2));
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (format === 'simple') {
|
|
62
|
-
renderSimple(messages);
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
renderTable(messages);
|
|
67
|
-
})
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
return scheduledCommand;
|
|
71
|
-
}
|
package/src/commands/send.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import { wrapCommand } from '../utils/command-wrapper';
|
|
4
|
-
import { ERROR_MESSAGES, SUCCESS_MESSAGES } from '../utils/constants';
|
|
5
|
-
import { createSlackClient } from '../utils/client-factory';
|
|
6
|
-
import { FileError } from '../utils/errors';
|
|
7
|
-
import { SendOptions } from '../types/commands';
|
|
8
|
-
import { extractErrorMessage } from '../utils/error-utils';
|
|
9
|
-
import { parseProfile } from '../utils/option-parsers';
|
|
10
|
-
import { createValidationHook, optionValidators } from '../utils/validators';
|
|
11
|
-
import { resolvePostAt } from '../utils/schedule-utils';
|
|
12
|
-
import * as fs from 'fs/promises';
|
|
13
|
-
|
|
14
|
-
export function setupSendCommand(): Command {
|
|
15
|
-
const sendCommand = new Command('send')
|
|
16
|
-
.description('Send or schedule a message to a Slack channel')
|
|
17
|
-
.requiredOption('-c, --channel <channel>', 'Target channel name or ID')
|
|
18
|
-
.option('-m, --message <message>', 'Message to send')
|
|
19
|
-
.option('-f, --file <file>', 'File containing message content')
|
|
20
|
-
.option('-t, --thread <thread>', 'Thread timestamp to reply to')
|
|
21
|
-
.option('--at <time>', 'Schedule time (Unix timestamp in seconds or ISO 8601)')
|
|
22
|
-
.option('--after <minutes>', 'Schedule message after N minutes')
|
|
23
|
-
.option('--profile <profile>', 'Use specific workspace profile')
|
|
24
|
-
.hook(
|
|
25
|
-
'preAction',
|
|
26
|
-
createValidationHook([
|
|
27
|
-
optionValidators.messageOrFile,
|
|
28
|
-
optionValidators.threadTimestamp,
|
|
29
|
-
optionValidators.scheduleTiming,
|
|
30
|
-
])
|
|
31
|
-
)
|
|
32
|
-
.action(
|
|
33
|
-
wrapCommand(async (options: SendOptions) => {
|
|
34
|
-
// Get message content
|
|
35
|
-
let messageContent: string;
|
|
36
|
-
if (options.file) {
|
|
37
|
-
try {
|
|
38
|
-
messageContent = await fs.readFile(options.file, 'utf-8');
|
|
39
|
-
} catch (error) {
|
|
40
|
-
throw new FileError(
|
|
41
|
-
ERROR_MESSAGES.FILE_READ_ERROR(options.file, extractErrorMessage(error))
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
} else {
|
|
45
|
-
messageContent = options.message!; // This is safe because of preAction validation
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const postAt = resolvePostAt(options.at, options.after);
|
|
49
|
-
|
|
50
|
-
// Send message
|
|
51
|
-
const profile = parseProfile(options.profile);
|
|
52
|
-
const client = await createSlackClient(profile);
|
|
53
|
-
|
|
54
|
-
if (postAt !== null) {
|
|
55
|
-
await client.scheduleMessage(options.channel, messageContent, postAt, options.thread);
|
|
56
|
-
const postAtIso = new Date(postAt * 1000).toISOString();
|
|
57
|
-
console.log(
|
|
58
|
-
chalk.green(`✓ ${SUCCESS_MESSAGES.MESSAGE_SCHEDULED(options.channel, postAtIso)}`)
|
|
59
|
-
);
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
await client.sendMessage(options.channel, messageContent, options.thread);
|
|
64
|
-
console.log(chalk.green(`✓ ${SUCCESS_MESSAGES.MESSAGE_SENT(options.channel)}`));
|
|
65
|
-
})
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
return sendCommand;
|
|
69
|
-
}
|
package/src/commands/unread.ts
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { wrapCommand } from '../utils/command-wrapper';
|
|
3
|
-
import { createSlackClient } from '../utils/client-factory';
|
|
4
|
-
import { SlackApiClient, ChannelUnreadResult, Channel } from '../utils/slack-api-client';
|
|
5
|
-
import { UnreadOptions } from '../types/commands';
|
|
6
|
-
import chalk from 'chalk';
|
|
7
|
-
import { createChannelFormatter } from '../utils/formatters/channel-formatters';
|
|
8
|
-
import { createMessageFormatter } from '../utils/formatters/message-formatters';
|
|
9
|
-
import { DEFAULTS } from '../utils/constants';
|
|
10
|
-
import { parseLimit, parseFormat, parseBoolean } from '../utils/option-parsers';
|
|
11
|
-
|
|
12
|
-
async function fetchChannelUnreadData(client: SlackApiClient, channelName: string) {
|
|
13
|
-
return await client.getChannelUnread(channelName);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function formatChannelUnreadOutput(
|
|
17
|
-
result: ChannelUnreadResult,
|
|
18
|
-
format: string,
|
|
19
|
-
countOnly: boolean
|
|
20
|
-
): void {
|
|
21
|
-
const formatter = createMessageFormatter(format);
|
|
22
|
-
formatter.format({
|
|
23
|
-
channel: result.channel,
|
|
24
|
-
messages: result.messages,
|
|
25
|
-
users: result.users,
|
|
26
|
-
countOnly: countOnly,
|
|
27
|
-
format: format,
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async function markChannelAsRead(client: SlackApiClient, channel: Channel): Promise<void> {
|
|
32
|
-
await client.markAsRead(channel.id);
|
|
33
|
-
console.log(chalk.green(`✓ Marked messages in #${channel.name} as read`));
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async function handleSpecificChannelUnread(
|
|
37
|
-
client: SlackApiClient,
|
|
38
|
-
options: UnreadOptions
|
|
39
|
-
): Promise<void> {
|
|
40
|
-
const result = await fetchChannelUnreadData(client, options.channel!);
|
|
41
|
-
|
|
42
|
-
const format = parseFormat(options.format);
|
|
43
|
-
const countOnly = parseBoolean(options.countOnly);
|
|
44
|
-
|
|
45
|
-
formatChannelUnreadOutput(result, format, countOnly);
|
|
46
|
-
|
|
47
|
-
if (parseBoolean(options.markRead)) {
|
|
48
|
-
await markChannelAsRead(client, result.channel);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async function fetchAllUnreadChannels(client: SlackApiClient) {
|
|
53
|
-
return await client.listUnreadChannels();
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function formatAllChannelsOutput(
|
|
57
|
-
channels: Channel[],
|
|
58
|
-
format: string,
|
|
59
|
-
countOnly: boolean,
|
|
60
|
-
limit: number
|
|
61
|
-
): void {
|
|
62
|
-
const displayChannels = channels.slice(0, limit);
|
|
63
|
-
const formatter = createChannelFormatter(format, countOnly);
|
|
64
|
-
formatter.format({ channels: displayChannels, countOnly: countOnly });
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async function markAllChannelsAsRead(client: SlackApiClient, channels: Channel[]): Promise<void> {
|
|
68
|
-
for (const channel of channels) {
|
|
69
|
-
await client.markAsRead(channel.id);
|
|
70
|
-
}
|
|
71
|
-
console.log(chalk.green('✓ Marked all messages as read'));
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async function handleAllChannelsUnread(
|
|
75
|
-
client: SlackApiClient,
|
|
76
|
-
options: UnreadOptions
|
|
77
|
-
): Promise<void> {
|
|
78
|
-
const channels = await fetchAllUnreadChannels(client);
|
|
79
|
-
|
|
80
|
-
if (channels.length === 0) {
|
|
81
|
-
console.log(chalk.green('✓ No unread messages'));
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const limit = parseLimit(options.limit, DEFAULTS.UNREAD_DISPLAY_LIMIT);
|
|
86
|
-
const format = parseFormat(options.format);
|
|
87
|
-
const countOnly = parseBoolean(options.countOnly);
|
|
88
|
-
|
|
89
|
-
formatAllChannelsOutput(channels, format, countOnly, limit);
|
|
90
|
-
|
|
91
|
-
if (parseBoolean(options.markRead)) {
|
|
92
|
-
await markAllChannelsAsRead(client, channels);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function setupUnreadCommand(): Command {
|
|
97
|
-
const unreadCommand = new Command('unread')
|
|
98
|
-
.description('Show unread messages across channels')
|
|
99
|
-
.option('-c, --channel <channel>', 'Show unread for a specific channel')
|
|
100
|
-
.option('--format <format>', 'Output format: table, simple, json', 'table')
|
|
101
|
-
.option('--count-only', 'Show only unread counts', false)
|
|
102
|
-
.option(
|
|
103
|
-
'--limit <number>',
|
|
104
|
-
'Maximum number of channels to display',
|
|
105
|
-
DEFAULTS.UNREAD_DISPLAY_LIMIT.toString()
|
|
106
|
-
)
|
|
107
|
-
.option('--mark-read', 'Mark messages as read after fetching', false)
|
|
108
|
-
.option('--profile <profile>', 'Use specific workspace profile')
|
|
109
|
-
.action(
|
|
110
|
-
wrapCommand(async (options: UnreadOptions) => {
|
|
111
|
-
const client = await createSlackClient(options.profile);
|
|
112
|
-
|
|
113
|
-
if (options.channel) {
|
|
114
|
-
await handleSpecificChannelUnread(client, options);
|
|
115
|
-
} else {
|
|
116
|
-
await handleAllChannelsUnread(client, options);
|
|
117
|
-
}
|
|
118
|
-
})
|
|
119
|
-
);
|
|
120
|
-
|
|
121
|
-
return unreadCommand;
|
|
122
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { Command } from 'commander';
|
|
3
|
-
import { setupConfigCommand } from './commands/config';
|
|
4
|
-
import { setupSendCommand } from './commands/send';
|
|
5
|
-
import { setupChannelsCommand } from './commands/channels';
|
|
6
|
-
import { setupHistoryCommand } from './commands/history';
|
|
7
|
-
import { setupUnreadCommand } from './commands/unread';
|
|
8
|
-
import { setupScheduledCommand } from './commands/scheduled';
|
|
9
|
-
import { readFileSync } from 'fs';
|
|
10
|
-
import { join } from 'path';
|
|
11
|
-
|
|
12
|
-
const program = new Command();
|
|
13
|
-
|
|
14
|
-
// Read version from package.json
|
|
15
|
-
const packageJson = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
|
|
16
|
-
const version = packageJson.version;
|
|
17
|
-
|
|
18
|
-
program.name('slack-cli').description('CLI tool to send messages via Slack API').version(version);
|
|
19
|
-
|
|
20
|
-
program.addCommand(setupConfigCommand());
|
|
21
|
-
program.addCommand(setupSendCommand());
|
|
22
|
-
program.addCommand(setupChannelsCommand());
|
|
23
|
-
program.addCommand(setupHistoryCommand());
|
|
24
|
-
program.addCommand(setupUnreadCommand());
|
|
25
|
-
program.addCommand(setupScheduledCommand());
|
|
26
|
-
|
|
27
|
-
program.parse();
|
package/src/types/commands.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
export interface ConfigSetOptions {
|
|
2
|
-
token: string;
|
|
3
|
-
profile?: string;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export interface ConfigGetOptions {
|
|
7
|
-
profile?: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface ConfigUseOptions {
|
|
11
|
-
profile: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface ConfigClearOptions {
|
|
15
|
-
profile?: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface SendOptions {
|
|
19
|
-
channel: string;
|
|
20
|
-
message?: string;
|
|
21
|
-
file?: string;
|
|
22
|
-
thread?: string;
|
|
23
|
-
at?: string;
|
|
24
|
-
after?: string;
|
|
25
|
-
profile?: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface ScheduledOptions {
|
|
29
|
-
channel?: string;
|
|
30
|
-
limit?: string;
|
|
31
|
-
format?: 'table' | 'simple' | 'json';
|
|
32
|
-
profile?: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface ChannelsOptions {
|
|
36
|
-
type: 'public' | 'private' | 'im' | 'mpim' | 'all';
|
|
37
|
-
includeArchived: boolean;
|
|
38
|
-
format: 'table' | 'simple' | 'json';
|
|
39
|
-
limit: string;
|
|
40
|
-
profile?: string;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface HistoryOptions {
|
|
44
|
-
channel: string;
|
|
45
|
-
number?: string;
|
|
46
|
-
since?: string;
|
|
47
|
-
format?: 'table' | 'simple' | 'json';
|
|
48
|
-
profile?: string;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export interface UnreadOptions {
|
|
52
|
-
channel?: string;
|
|
53
|
-
format?: 'table' | 'simple' | 'json';
|
|
54
|
-
countOnly?: boolean;
|
|
55
|
-
limit?: string;
|
|
56
|
-
markRead?: boolean;
|
|
57
|
-
profile?: string;
|
|
58
|
-
}
|
package/src/types/config.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export interface Config {
|
|
2
|
-
token: string;
|
|
3
|
-
updatedAt: string;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export interface Profile {
|
|
7
|
-
name: string;
|
|
8
|
-
config: Config;
|
|
9
|
-
isDefault?: boolean;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface ConfigStore {
|
|
13
|
-
profiles: Record<string, Config>;
|
|
14
|
-
defaultProfile?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface ConfigOptions {
|
|
18
|
-
configDir?: string;
|
|
19
|
-
profile?: string;
|
|
20
|
-
}
|