@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.
Files changed (97) hide show
  1. package/README.md +4 -0
  2. package/dist/commands/history-display.d.ts +5 -1
  3. package/dist/commands/history-display.d.ts.map +1 -1
  4. package/dist/commands/history-display.js +3 -3
  5. package/dist/commands/history-display.js.map +1 -1
  6. package/dist/commands/history.d.ts.map +1 -1
  7. package/dist/commands/history.js +28 -11
  8. package/dist/commands/history.js.map +1 -1
  9. package/dist/types/commands.d.ts +1 -0
  10. package/dist/types/commands.d.ts.map +1 -1
  11. package/dist/utils/slack-api-client.d.ts +1 -0
  12. package/dist/utils/slack-api-client.d.ts.map +1 -1
  13. package/dist/utils/slack-api-client.js +3 -0
  14. package/dist/utils/slack-api-client.js.map +1 -1
  15. package/dist/utils/slack-operations/message-operations.d.ts +1 -0
  16. package/dist/utils/slack-operations/message-operations.d.ts.map +1 -1
  17. package/dist/utils/slack-operations/message-operations.js +21 -0
  18. package/dist/utils/slack-operations/message-operations.js.map +1 -1
  19. package/package.json +5 -2
  20. package/.claude/settings.local.json +0 -75
  21. package/.github/dependabot.yml +0 -18
  22. package/.github/workflows/ci.yml +0 -70
  23. package/.github/workflows/pr-validation.yml +0 -41
  24. package/.prettierignore +0 -11
  25. package/.prettierrc +0 -10
  26. package/CHANGELOG.md +0 -61
  27. package/CLAUDE.md +0 -16
  28. package/eslint.config.js +0 -38
  29. package/src/commands/channels.ts +0 -50
  30. package/src/commands/config-subcommands.ts +0 -63
  31. package/src/commands/config.ts +0 -50
  32. package/src/commands/history-display.ts +0 -19
  33. package/src/commands/history-validators.ts +0 -46
  34. package/src/commands/history.ts +0 -61
  35. package/src/commands/scheduled.ts +0 -71
  36. package/src/commands/send.ts +0 -69
  37. package/src/commands/unread.ts +0 -122
  38. package/src/index.ts +0 -27
  39. package/src/types/commands.ts +0 -58
  40. package/src/types/config.ts +0 -20
  41. package/src/utils/channel-formatter.ts +0 -45
  42. package/src/utils/channel-resolver.ts +0 -82
  43. package/src/utils/client-factory.ts +0 -10
  44. package/src/utils/command-wrapper.ts +0 -27
  45. package/src/utils/config/config-file-manager.ts +0 -56
  46. package/src/utils/config/profile-manager.ts +0 -79
  47. package/src/utils/config/token-crypto-service.ts +0 -80
  48. package/src/utils/config-helper.ts +0 -21
  49. package/src/utils/constants.ts +0 -78
  50. package/src/utils/date-utils.ts +0 -8
  51. package/src/utils/error-utils.ts +0 -6
  52. package/src/utils/errors.ts +0 -33
  53. package/src/utils/format-utils.ts +0 -9
  54. package/src/utils/formatters/base-formatter.ts +0 -34
  55. package/src/utils/formatters/channel-formatters.ts +0 -71
  56. package/src/utils/formatters/channels-list-formatters.ts +0 -55
  57. package/src/utils/formatters/history-formatters.ts +0 -123
  58. package/src/utils/formatters/message-formatters.ts +0 -85
  59. package/src/utils/mention-utils.ts +0 -47
  60. package/src/utils/option-parsers.ts +0 -100
  61. package/src/utils/profile-config.ts +0 -161
  62. package/src/utils/schedule-utils.ts +0 -41
  63. package/src/utils/slack-api-client.ts +0 -135
  64. package/src/utils/slack-operations/base-client.ts +0 -30
  65. package/src/utils/slack-operations/channel-operations.ts +0 -161
  66. package/src/utils/slack-operations/index.ts +0 -3
  67. package/src/utils/slack-operations/message-operations.ts +0 -176
  68. package/src/utils/slack-patterns.ts +0 -9
  69. package/src/utils/token-utils.ts +0 -17
  70. package/src/utils/validators.ts +0 -263
  71. package/tests/commands/channels.test.ts +0 -250
  72. package/tests/commands/config.test.ts +0 -158
  73. package/tests/commands/history.test.ts +0 -403
  74. package/tests/commands/scheduled.test.ts +0 -131
  75. package/tests/commands/send.test.ts +0 -414
  76. package/tests/commands/unread.test.ts +0 -492
  77. package/tests/index.test.ts +0 -40
  78. package/tests/test-utils.ts +0 -28
  79. package/tests/utils/channel-resolver.test.ts +0 -161
  80. package/tests/utils/config/config-file-manager.test.ts +0 -118
  81. package/tests/utils/config/profile-manager.test.ts +0 -266
  82. package/tests/utils/config/token-crypto-service.test.ts +0 -98
  83. package/tests/utils/config.test.ts +0 -400
  84. package/tests/utils/date-utils.test.ts +0 -30
  85. package/tests/utils/error-utils.test.ts +0 -34
  86. package/tests/utils/format-utils.test.ts +0 -61
  87. package/tests/utils/mention-utils.test.ts +0 -100
  88. package/tests/utils/option-parsers.test.ts +0 -173
  89. package/tests/utils/profile-config.test.ts +0 -282
  90. package/tests/utils/schedule-utils.test.ts +0 -63
  91. package/tests/utils/slack-api-client.test.ts +0 -313
  92. package/tests/utils/slack-operations/channel-operations.test.ts +0 -248
  93. package/tests/utils/slack-operations/message-operations.test.ts +0 -163
  94. package/tests/utils/token-utils.test.ts +0 -33
  95. package/tests/utils/validators.test.ts +0 -307
  96. package/tsconfig.json +0 -22
  97. package/vitest.config.ts +0 -27
@@ -1,55 +0,0 @@
1
- import { AbstractFormatter, JsonFormatter, createFormatterFactory } from './base-formatter';
2
- import { ChannelInfo } from '../channel-formatter';
3
-
4
- export interface ChannelsListFormatterOptions {
5
- channels: ChannelInfo[];
6
- }
7
-
8
- class ChannelsTableFormatter extends AbstractFormatter<ChannelsListFormatterOptions> {
9
- format({ channels }: ChannelsListFormatterOptions): void {
10
- // Print table header
11
- console.log('Name Type Members Created Description');
12
- console.log('─'.repeat(65));
13
-
14
- // Print channel rows
15
- channels.forEach((channel) => {
16
- const name = channel.name.padEnd(17);
17
- const type = channel.type.padEnd(9);
18
- const members = channel.members.toString().padEnd(8);
19
- const created = channel.created.padEnd(12);
20
- const purpose =
21
- channel.purpose.length > 30 ? channel.purpose.substring(0, 27) + '...' : channel.purpose;
22
-
23
- console.log(`${name} ${type} ${members} ${created} ${purpose}`);
24
- });
25
- }
26
- }
27
-
28
- class ChannelsSimpleFormatter extends AbstractFormatter<ChannelsListFormatterOptions> {
29
- format({ channels }: ChannelsListFormatterOptions): void {
30
- channels.forEach((channel) => console.log(channel.name));
31
- }
32
- }
33
-
34
- class ChannelsJsonFormatter extends JsonFormatter<ChannelsListFormatterOptions> {
35
- protected transform({ channels }: ChannelsListFormatterOptions) {
36
- return channels.map((channel) => ({
37
- id: channel.id,
38
- name: channel.name,
39
- type: channel.type,
40
- members: channel.members,
41
- created: channel.created + 'T00:00:00Z',
42
- purpose: channel.purpose,
43
- }));
44
- }
45
- }
46
-
47
- const channelsListFormatterFactory = createFormatterFactory<ChannelsListFormatterOptions>({
48
- table: new ChannelsTableFormatter(),
49
- simple: new ChannelsSimpleFormatter(),
50
- json: new ChannelsJsonFormatter(),
51
- });
52
-
53
- export function createChannelsListFormatter(format: string) {
54
- return channelsListFormatterFactory.create(format);
55
- }
@@ -1,123 +0,0 @@
1
- import chalk from 'chalk';
2
- import { AbstractFormatter, JsonFormatter, createFormatterFactory } from './base-formatter';
3
- import { formatSlackTimestamp } from '../date-utils';
4
-
5
- function formatTimestampFixed(slackTimestamp: string): string {
6
- const timestamp = parseFloat(slackTimestamp);
7
- const date = new Date(timestamp * 1000);
8
- const year = date.getUTCFullYear();
9
- const month = String(date.getUTCMonth() + 1).padStart(2, '0');
10
- const day = String(date.getUTCDate()).padStart(2, '0');
11
- const hours = String(date.getUTCHours()).padStart(2, '0');
12
- const minutes = String(date.getUTCMinutes()).padStart(2, '0');
13
- const seconds = String(date.getUTCSeconds()).padStart(2, '0');
14
- return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
15
- }
16
- import { formatMessageWithMentions } from '../format-utils';
17
- import { Message as SlackMessage } from '../slack-api-client';
18
-
19
- export interface HistoryFormatterOptions {
20
- channelName: string;
21
- messages: SlackMessage[];
22
- users: Map<string, string>;
23
- }
24
-
25
- class TableHistoryFormatter extends AbstractFormatter<HistoryFormatterOptions> {
26
- format(options: HistoryFormatterOptions): void {
27
- const { channelName, messages, users } = options;
28
-
29
- console.log(chalk.bold(`\nMessage History for #${channelName}:`));
30
-
31
- if (messages.length === 0) {
32
- console.log(chalk.yellow('No messages found'));
33
- return;
34
- }
35
-
36
- console.log('');
37
- messages.forEach((message) => {
38
- const timestamp = formatTimestampFixed(message.ts);
39
- const username = this.getUsername(message, users);
40
-
41
- console.log(`${chalk.gray(`[${timestamp}]`)} ${chalk.cyan(username)}`);
42
- const text = message.text ? formatMessageWithMentions(message.text, users) : '(no text)';
43
- console.log(text);
44
- console.log('');
45
- });
46
-
47
- console.log(chalk.green(`✓ Displayed ${messages.length} message(s)`));
48
- }
49
-
50
- private getUsername(message: SlackMessage, users: Map<string, string>): string {
51
- if (message.user) {
52
- return users.get(message.user) || 'Unknown User';
53
- }
54
- if (message.bot_id) {
55
- return 'Bot';
56
- }
57
- return 'Unknown';
58
- }
59
- }
60
-
61
- class SimpleHistoryFormatter extends AbstractFormatter<HistoryFormatterOptions> {
62
- format(options: HistoryFormatterOptions): void {
63
- const { messages, users } = options;
64
-
65
- if (messages.length === 0) {
66
- console.log('No messages found');
67
- return;
68
- }
69
-
70
- messages.forEach((message) => {
71
- const timestamp = formatTimestampFixed(message.ts);
72
- const username = this.getUsername(message, users);
73
- const text = message.text ? formatMessageWithMentions(message.text, users) : '(no text)';
74
- console.log(`[${timestamp}] ${username}: ${text}`);
75
- });
76
- }
77
-
78
- private getUsername(message: SlackMessage, users: Map<string, string>): string {
79
- if (message.user) {
80
- return users.get(message.user) || 'Unknown User';
81
- }
82
- if (message.bot_id) {
83
- return 'Bot';
84
- }
85
- return 'Unknown';
86
- }
87
- }
88
-
89
- class JsonHistoryFormatter extends JsonFormatter<HistoryFormatterOptions> {
90
- protected transform(options: HistoryFormatterOptions) {
91
- const { channelName, messages, users } = options;
92
-
93
- return {
94
- channel: channelName,
95
- messages: messages.map((message) => ({
96
- timestamp: formatTimestampFixed(message.ts),
97
- user: this.getUsername(message, users),
98
- text: message.text || '(no text)',
99
- })),
100
- total: messages.length,
101
- };
102
- }
103
-
104
- private getUsername(message: SlackMessage, users: Map<string, string>): string {
105
- if (message.user) {
106
- return users.get(message.user) || 'Unknown User';
107
- }
108
- if (message.bot_id) {
109
- return 'Bot';
110
- }
111
- return 'Unknown';
112
- }
113
- }
114
-
115
- const historyFormatterFactory = createFormatterFactory<HistoryFormatterOptions>({
116
- table: new TableHistoryFormatter(),
117
- simple: new SimpleHistoryFormatter(),
118
- json: new JsonHistoryFormatter(),
119
- });
120
-
121
- export function createHistoryFormatter(format: string) {
122
- return historyFormatterFactory.create(format);
123
- }
@@ -1,85 +0,0 @@
1
- import chalk from 'chalk';
2
- import { AbstractFormatter, JsonFormatter, createFormatterFactory } from './base-formatter';
3
- import { formatSlackTimestamp } from '../date-utils';
4
- import { formatMessageWithMentions } from '../format-utils';
5
- import { formatChannelName } from '../channel-formatter';
6
-
7
- export interface MessageFormatterOptions {
8
- channel: any;
9
- messages: any[];
10
- users: Map<string, string>;
11
- countOnly: boolean;
12
- format: string;
13
- }
14
-
15
- class TableMessageFormatter extends AbstractFormatter<MessageFormatterOptions> {
16
- format(options: MessageFormatterOptions): void {
17
- const { channel, messages, users, countOnly } = options;
18
- const channelName = formatChannelName(channel.name);
19
-
20
- console.log(chalk.bold(`${channelName}: ${channel.unread_count || 0} unread messages`));
21
-
22
- if (!countOnly && messages.length > 0) {
23
- console.log('');
24
- messages.forEach((message) => {
25
- const timestamp = formatSlackTimestamp(message.ts);
26
- const author = message.user ? users.get(message.user) || message.user : 'unknown';
27
- console.log(`${chalk.gray(timestamp)} ${chalk.cyan(author)}`);
28
- const text = message.text ? formatMessageWithMentions(message.text, users) : '(no text)';
29
- console.log(text);
30
- console.log('');
31
- });
32
- }
33
- }
34
- }
35
-
36
- class SimpleMessageFormatter extends AbstractFormatter<MessageFormatterOptions> {
37
- format(options: MessageFormatterOptions): void {
38
- const { channel, messages, users, countOnly } = options;
39
- const channelName = formatChannelName(channel.name);
40
-
41
- console.log(`${channelName} (${channel.unread_count || 0})`);
42
-
43
- if (!countOnly && messages.length > 0) {
44
- messages.forEach((message) => {
45
- const timestamp = formatSlackTimestamp(message.ts);
46
- const author = message.user ? users.get(message.user) || message.user : 'unknown';
47
- const text = message.text ? formatMessageWithMentions(message.text, users) : '(no text)';
48
- console.log(`[${timestamp}] ${author}: ${text}`);
49
- });
50
- }
51
- }
52
- }
53
-
54
- class JsonMessageFormatter extends JsonFormatter<MessageFormatterOptions> {
55
- protected transform(options: MessageFormatterOptions) {
56
- const { channel, messages, users, countOnly } = options;
57
- const channelName = formatChannelName(channel.name);
58
-
59
- const output: any = {
60
- channel: channelName,
61
- channelId: channel.id,
62
- unreadCount: channel.unread_count || 0,
63
- };
64
-
65
- if (!countOnly && messages.length > 0) {
66
- output.messages = messages.map((message) => ({
67
- timestamp: formatSlackTimestamp(message.ts),
68
- author: message.user ? users.get(message.user) || message.user : 'unknown',
69
- text: message.text || '(no text)',
70
- }));
71
- }
72
-
73
- return output;
74
- }
75
- }
76
-
77
- const messageFormatterFactory = createFormatterFactory<MessageFormatterOptions>({
78
- table: new TableMessageFormatter(),
79
- simple: new SimpleMessageFormatter(),
80
- json: new JsonMessageFormatter(),
81
- });
82
-
83
- export function createMessageFormatter(format: string) {
84
- return messageFormatterFactory.create(format);
85
- }
@@ -1,47 +0,0 @@
1
- import { USER_MENTION_PATTERN } from './slack-patterns';
2
-
3
- /**
4
- * Extracts all user IDs from mentions in a text
5
- * @param text - The text containing Slack mentions
6
- * @returns Array of unique user IDs found in mentions
7
- */
8
- export function extractUserIdsFromMentions(text: string): string[] {
9
- const userIds: string[] = [];
10
- const matches = text.matchAll(USER_MENTION_PATTERN);
11
-
12
- for (const match of matches) {
13
- const userId = match[1];
14
- if (userId) {
15
- userIds.push(userId);
16
- }
17
- }
18
-
19
- return userIds;
20
- }
21
-
22
- /**
23
- * Extracts all unique user IDs from an array of messages
24
- * Includes both message authors and mentioned users
25
- * @param messages - Array of messages to extract user IDs from
26
- * @returns Array of unique user IDs
27
- */
28
- export function extractAllUserIds(messages: Array<{ user?: string; text?: string }>): string[] {
29
- const userIds = new Set<string>();
30
-
31
- for (const message of messages) {
32
- // Add message author
33
- if (message.user) {
34
- userIds.add(message.user);
35
- }
36
-
37
- // Add mentioned users
38
- if (message.text) {
39
- const mentionedIds = extractUserIdsFromMentions(message.text);
40
- for (const id of mentionedIds) {
41
- userIds.add(id);
42
- }
43
- }
44
- }
45
-
46
- return Array.from(userIds);
47
- }
@@ -1,100 +0,0 @@
1
- /**
2
- * Common option parsing utilities to reduce duplication
3
- */
4
-
5
- /**
6
- * Parse format option with default value
7
- */
8
- export function parseFormat(format?: string, defaultFormat = 'table'): string {
9
- return format || defaultFormat;
10
- }
11
-
12
- /**
13
- * Parse limit option with default value
14
- */
15
- export function parseLimit(limit: string | undefined, defaultLimit: number): number {
16
- return parseInt(limit || defaultLimit.toString(), 10);
17
- }
18
-
19
- /**
20
- * Parse boolean option with default value
21
- */
22
- export function parseBoolean(value?: boolean, defaultValue = false): boolean {
23
- return value !== undefined ? value : defaultValue;
24
- }
25
-
26
- /**
27
- * Parse count option with default value
28
- */
29
- export function parseCount(
30
- count: string | undefined,
31
- defaultCount: number,
32
- min?: number,
33
- max?: number
34
- ): number {
35
- const parsed = parseInt(count || defaultCount.toString(), 10);
36
-
37
- if (isNaN(parsed)) {
38
- return defaultCount;
39
- }
40
-
41
- if (min !== undefined && parsed < min) {
42
- return min;
43
- }
44
-
45
- if (max !== undefined && parsed > max) {
46
- return max;
47
- }
48
-
49
- return parsed;
50
- }
51
-
52
- /**
53
- * Parse profile option
54
- */
55
- export function parseProfile(profile?: string): string | undefined {
56
- return profile;
57
- }
58
-
59
- /**
60
- * Common option defaults
61
- */
62
- export const OPTION_DEFAULTS = {
63
- format: 'table',
64
- limit: 100,
65
- countOnly: false,
66
- includeArchived: false,
67
- } as const;
68
-
69
- /**
70
- * Parse common list options
71
- */
72
- export interface ListOptions {
73
- format?: string;
74
- limit?: string;
75
- countOnly?: boolean;
76
- }
77
-
78
- export interface ParsedListOptions {
79
- format: string;
80
- limit: number;
81
- countOnly: boolean;
82
- }
83
-
84
- export function parseListOptions(
85
- options: ListOptions,
86
- defaults?: Partial<ParsedListOptions>
87
- ): ParsedListOptions {
88
- const mergedDefaults = {
89
- format: OPTION_DEFAULTS.format,
90
- limit: OPTION_DEFAULTS.limit,
91
- countOnly: OPTION_DEFAULTS.countOnly,
92
- ...defaults,
93
- };
94
-
95
- return {
96
- format: parseFormat(options.format, mergedDefaults.format),
97
- limit: parseLimit(options.limit, mergedDefaults.limit),
98
- countOnly: parseBoolean(options.countOnly, mergedDefaults.countOnly),
99
- };
100
- }
@@ -1,161 +0,0 @@
1
- import * as fs from 'fs/promises';
2
- import * as path from 'path';
3
- import * as os from 'os';
4
- import type { Config, ConfigOptions, ConfigStore, Profile } from '../types/config';
5
- import { DEFAULT_PROFILE_NAME, ERROR_MESSAGES, FILE_PERMISSIONS } from './constants';
6
- import { maskToken } from './token-utils';
7
-
8
- export class ProfileConfigManager {
9
- private configPath: string;
10
-
11
- constructor(options: ConfigOptions = {}) {
12
- const configDir = options.configDir || path.join(os.homedir(), '.slack-cli');
13
- this.configPath = path.join(configDir, 'config.json');
14
- }
15
-
16
- async setToken(token: string, profile?: string): Promise<void> {
17
- const store = await this.getConfigStore();
18
- const profileName = profile || store.defaultProfile || DEFAULT_PROFILE_NAME;
19
- const config: Config = {
20
- token,
21
- updatedAt: new Date().toISOString(),
22
- };
23
-
24
- store.profiles[profileName] = config;
25
-
26
- // Set as default profile if it's the first one or explicitly setting default
27
- if (!store.defaultProfile || profileName === DEFAULT_PROFILE_NAME) {
28
- store.defaultProfile = profileName;
29
- }
30
-
31
- await this.saveConfigStore(store);
32
- }
33
-
34
- async getConfig(profile?: string): Promise<Config | null> {
35
- const store = await this.getConfigStore();
36
- const profileName = profile || store.defaultProfile || DEFAULT_PROFILE_NAME;
37
-
38
- return store.profiles[profileName] || null;
39
- }
40
-
41
- async listProfiles(): Promise<Profile[]> {
42
- const store = await this.getConfigStore();
43
- const currentProfile = store.defaultProfile || DEFAULT_PROFILE_NAME;
44
-
45
- return Object.entries(store.profiles).map(([name, config]) => ({
46
- name,
47
- config,
48
- isDefault: name === currentProfile,
49
- }));
50
- }
51
-
52
- async useProfile(profile: string): Promise<void> {
53
- const store = await this.getConfigStore();
54
-
55
- if (!store.profiles[profile]) {
56
- throw new Error(`Profile "${profile}" does not exist`);
57
- }
58
-
59
- store.defaultProfile = profile;
60
- await this.saveConfigStore(store);
61
- }
62
-
63
- async getCurrentProfile(): Promise<string> {
64
- const store = await this.getConfigStore();
65
- return store.defaultProfile || DEFAULT_PROFILE_NAME;
66
- }
67
-
68
- async clearConfig(profile?: string): Promise<void> {
69
- const store = await this.getConfigStore();
70
- const profileName = profile || store.defaultProfile || DEFAULT_PROFILE_NAME;
71
-
72
- delete store.profiles[profileName];
73
-
74
- // If we deleted the default profile, set a new default
75
- if (store.defaultProfile === profileName) {
76
- const remainingProfiles = Object.keys(store.profiles);
77
- if (remainingProfiles.length > 0) {
78
- store.defaultProfile = remainingProfiles[0];
79
- } else {
80
- // No profiles left, delete the config file
81
- try {
82
- await fs.unlink(this.configPath);
83
- } catch (error: unknown) {
84
- if (error && typeof error === 'object' && 'code' in error && error.code !== 'ENOENT') {
85
- throw error;
86
- }
87
- }
88
- return;
89
- }
90
- }
91
-
92
- await this.saveConfigStore(store);
93
- }
94
-
95
- maskToken(token: string): string {
96
- return maskToken(token);
97
- }
98
-
99
- private async getConfigStore(): Promise<ConfigStore> {
100
- try {
101
- const data = await fs.readFile(this.configPath, 'utf-8');
102
- const parsed = JSON.parse(data);
103
-
104
- // Handle migration from old format
105
- if (this.needsMigration(parsed)) {
106
- return await this.migrateOldConfig(parsed);
107
- }
108
-
109
- return parsed as ConfigStore;
110
- } catch (error: unknown) {
111
- if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
112
- return { profiles: {} };
113
- }
114
- if (error instanceof SyntaxError) {
115
- throw new Error(ERROR_MESSAGES.INVALID_CONFIG_FORMAT);
116
- }
117
- throw error;
118
- }
119
- }
120
-
121
- private needsMigration(data: unknown): boolean {
122
- const configData = data as Record<string, unknown>;
123
- return Boolean(configData.token && !configData.profiles);
124
- }
125
-
126
- private async migrateOldConfig(oldData: unknown): Promise<ConfigStore> {
127
- const data = oldData as { token: string; updatedAt: string };
128
- const oldConfig: Config = {
129
- token: data.token,
130
- updatedAt: data.updatedAt,
131
- };
132
-
133
- const newStore: ConfigStore = {
134
- profiles: { [DEFAULT_PROFILE_NAME]: oldConfig },
135
- defaultProfile: DEFAULT_PROFILE_NAME,
136
- };
137
-
138
- // Save migrated config
139
- await this.saveConfigStore(newStore);
140
- return newStore;
141
- }
142
-
143
- private async saveConfigStore(store: ConfigStore): Promise<void> {
144
- const configDir = path.dirname(this.configPath);
145
- await fs.mkdir(configDir, { recursive: true });
146
-
147
- await fs.writeFile(this.configPath, JSON.stringify(store, null, 2));
148
- await fs.chmod(this.configPath, FILE_PERMISSIONS.CONFIG_FILE);
149
- }
150
- }
151
-
152
- export const profileConfig = {
153
- getCurrentProfile: (): string => {
154
- return DEFAULT_PROFILE_NAME;
155
- },
156
- getToken: (_profile?: string): string | undefined => {
157
- // This is a simplified version for testing
158
- // In real usage, it would need to be async
159
- return undefined;
160
- },
161
- };
@@ -1,41 +0,0 @@
1
- export function parseScheduledTimestamp(value: string): number | null {
2
- const trimmed = value.trim();
3
-
4
- if (/^\d+$/.test(trimmed)) {
5
- const timestamp = Number.parseInt(trimmed, 10);
6
- return Number.isSafeInteger(timestamp) ? timestamp : null;
7
- }
8
-
9
- const parsedMs = Date.parse(trimmed);
10
- if (Number.isNaN(parsedMs)) {
11
- return null;
12
- }
13
-
14
- return Math.floor(parsedMs / 1000);
15
- }
16
-
17
- export function resolvePostAt(
18
- at: string | undefined,
19
- afterMinutes: string | undefined,
20
- nowMs = Date.now()
21
- ): number | null {
22
- if (at) {
23
- return parseScheduledTimestamp(at);
24
- }
25
-
26
- if (!afterMinutes) {
27
- return null;
28
- }
29
-
30
- const trimmedAfter = afterMinutes.trim();
31
- if (!/^\d+$/.test(trimmedAfter)) {
32
- return null;
33
- }
34
-
35
- const minutes = Number.parseInt(trimmedAfter, 10);
36
- if (!Number.isSafeInteger(minutes) || minutes <= 0) {
37
- return null;
38
- }
39
-
40
- return Math.floor(nowMs / 1000) + minutes * 60;
41
- }