@urugus/slack-cli 0.1.4 → 0.1.6

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 (43) hide show
  1. package/.claude/settings.local.json +2 -1
  2. package/CHANGELOG.md +31 -0
  3. package/dist/commands/unread.d.ts.map +1 -1
  4. package/dist/commands/unread.js +6 -49
  5. package/dist/commands/unread.js.map +1 -1
  6. package/dist/index.js +6 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/utils/channel-resolver.d.ts +26 -0
  9. package/dist/utils/channel-resolver.d.ts.map +1 -0
  10. package/dist/utils/channel-resolver.js +74 -0
  11. package/dist/utils/channel-resolver.js.map +1 -0
  12. package/dist/utils/constants.d.ts +17 -0
  13. package/dist/utils/constants.d.ts.map +1 -1
  14. package/dist/utils/constants.js +21 -1
  15. package/dist/utils/constants.js.map +1 -1
  16. package/dist/utils/formatters/channel-formatters.d.ts +16 -0
  17. package/dist/utils/formatters/channel-formatters.d.ts.map +1 -0
  18. package/dist/utils/formatters/channel-formatters.js +73 -0
  19. package/dist/utils/formatters/channel-formatters.js.map +1 -0
  20. package/dist/utils/formatters/output-formatter.d.ts +7 -0
  21. package/dist/utils/formatters/output-formatter.d.ts.map +1 -0
  22. package/dist/utils/formatters/output-formatter.js +7 -0
  23. package/dist/utils/formatters/output-formatter.js.map +1 -0
  24. package/dist/utils/profile-config.d.ts.map +1 -1
  25. package/dist/utils/profile-config.js +5 -3
  26. package/dist/utils/profile-config.js.map +1 -1
  27. package/dist/utils/slack-api-client.d.ts +1 -0
  28. package/dist/utils/slack-api-client.d.ts.map +1 -1
  29. package/dist/utils/slack-api-client.js +59 -101
  30. package/dist/utils/slack-api-client.js.map +1 -1
  31. package/package.json +5 -4
  32. package/src/commands/unread.ts +11 -52
  33. package/src/index.ts +7 -1
  34. package/src/utils/channel-resolver.ts +86 -0
  35. package/src/utils/constants.ts +23 -0
  36. package/src/utils/formatters/channel-formatters.ts +69 -0
  37. package/src/utils/formatters/output-formatter.ts +7 -0
  38. package/src/utils/profile-config.ts +7 -5
  39. package/src/utils/slack-api-client.ts +73 -106
  40. package/tests/commands/unread.test.ts +31 -0
  41. package/tests/index.test.ts +40 -0
  42. package/tests/utils/channel-resolver.test.ts +157 -0
  43. package/tests/utils/slack-api-client.test.ts +8 -1
@@ -1,10 +1,20 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.slackApiClient = exports.SlackApiClient = void 0;
4
7
  const web_api_1 = require("@slack/web-api");
8
+ const p_limit_1 = __importDefault(require("p-limit"));
9
+ const channel_resolver_1 = require("./channel-resolver");
10
+ const constants_1 = require("./constants");
5
11
  class SlackApiClient {
6
12
  constructor(token) {
7
- this.client = new web_api_1.WebClient(token);
13
+ this.client = new web_api_1.WebClient(token, {
14
+ retryConfig: constants_1.RATE_LIMIT.RETRY_CONFIG,
15
+ });
16
+ // Limit concurrent API calls to avoid rate limiting
17
+ this.rateLimiter = (0, p_limit_1.default)(constants_1.RATE_LIMIT.CONCURRENT_REQUESTS);
8
18
  }
9
19
  async sendMessage(channel, text) {
10
20
  return await this.client.chat.postMessage({
@@ -31,46 +41,12 @@ class SlackApiClient {
31
41
  return channels;
32
42
  }
33
43
  async getHistory(channel, options) {
34
- // First, resolve channel name to ID if needed
35
- let channelId = channel;
36
- if (!channel.startsWith('C') && !channel.startsWith('D') && !channel.startsWith('G')) {
37
- // It's a name, not an ID - need to find the ID
38
- const channels = await this.listChannels({
39
- types: 'public_channel,private_channel,im,mpim',
40
- exclude_archived: true,
41
- limit: 1000,
42
- });
43
- // Try multiple matching strategies
44
- const foundChannel = channels.find((c) => {
45
- // Direct name match
46
- if (c.name === channel)
47
- return true;
48
- // Match without # prefix
49
- if (c.name === channel.replace('#', ''))
50
- return true;
51
- // Case-insensitive match
52
- if (c.name?.toLowerCase() === channel.toLowerCase())
53
- return true;
54
- // Match with normalized name
55
- if (c.name_normalized === channel)
56
- return true;
57
- return false;
58
- });
59
- if (!foundChannel) {
60
- // Provide helpful error message
61
- const similarChannels = channels
62
- .filter((c) => c.name?.toLowerCase().includes(channel.toLowerCase()))
63
- .slice(0, 5)
64
- .map((c) => c.name);
65
- if (similarChannels.length > 0) {
66
- throw new Error(`Channel '${channel}' not found. Did you mean one of these? ${similarChannels.join(', ')}`);
67
- }
68
- else {
69
- throw new Error(`Channel '${channel}' not found. Make sure you are a member of this channel.`);
70
- }
71
- }
72
- channelId = foundChannel.id;
73
- }
44
+ // Resolve channel name to ID if needed
45
+ const channelId = await channel_resolver_1.channelResolver.resolveChannelId(channel, () => this.listChannels({
46
+ types: 'public_channel,private_channel,im,mpim',
47
+ exclude_archived: true,
48
+ limit: constants_1.DEFAULTS.CHANNELS_LIMIT,
49
+ }));
74
50
  const response = await this.client.conversations.history({
75
51
  channel: channelId,
76
52
  limit: options.limit,
@@ -105,71 +81,53 @@ class SlackApiClient {
105
81
  limit: 1000,
106
82
  });
107
83
  const channels = response.channels;
108
- // Get unread count for each channel
109
- const channelsWithUnread = await Promise.all(channels.map(async (channel) => {
110
- try {
111
- const info = await this.client.conversations.info({
112
- channel: channel.id,
113
- include_num_members: false,
114
- });
115
- const channelInfo = info.channel;
116
- return {
117
- ...channel,
118
- unread_count: channelInfo.unread_count || 0,
119
- unread_count_display: channelInfo.unread_count_display || 0,
120
- last_read: channelInfo.last_read,
121
- };
122
- }
123
- catch {
124
- return channel;
84
+ // Batch process channels to reduce rate limit issues
85
+ const batchSize = constants_1.RATE_LIMIT.BATCH_SIZE;
86
+ const batches = [];
87
+ for (let i = 0; i < channels.length; i += batchSize) {
88
+ batches.push(channels.slice(i, i + batchSize));
89
+ }
90
+ const channelsWithUnread = [];
91
+ // Process batches sequentially with delay
92
+ for (const batch of batches) {
93
+ const batchResults = await Promise.all(batch.map((channel) => this.rateLimiter(async () => {
94
+ try {
95
+ const info = await this.client.conversations.info({
96
+ channel: channel.id,
97
+ include_num_members: false,
98
+ });
99
+ const channelInfo = info.channel;
100
+ return {
101
+ ...channel,
102
+ unread_count: channelInfo.unread_count || 0,
103
+ unread_count_display: channelInfo.unread_count_display || 0,
104
+ last_read: channelInfo.last_read,
105
+ };
106
+ }
107
+ catch (error) {
108
+ // Log rate limit errors but continue processing
109
+ if (error instanceof Error && error.message?.includes('rate limit')) {
110
+ console.warn(`Rate limit hit for channel ${channel.name}, skipping...`);
111
+ }
112
+ return channel;
113
+ }
114
+ })));
115
+ channelsWithUnread.push(...batchResults);
116
+ // Add delay between batches to avoid rate limiting
117
+ if (batches.indexOf(batch) < batches.length - 1) {
118
+ await new Promise((resolve) => setTimeout(resolve, constants_1.RATE_LIMIT.BATCH_DELAY_MS));
125
119
  }
126
- }));
120
+ }
127
121
  // Filter to only channels with unread messages
128
122
  return channelsWithUnread.filter((channel) => (channel.unread_count_display || 0) > 0);
129
123
  }
130
124
  async getChannelUnread(channelNameOrId) {
131
- // First, find the channel
132
- let channelId = channelNameOrId;
133
- if (!channelNameOrId.startsWith('C') &&
134
- !channelNameOrId.startsWith('D') &&
135
- !channelNameOrId.startsWith('G')) {
136
- // It's a name, not an ID - need to find the ID
137
- const channels = await this.listChannels({
138
- types: 'public_channel,private_channel,im,mpim',
139
- exclude_archived: true,
140
- limit: 1000,
141
- });
142
- // Try multiple matching strategies (same as getHistory)
143
- const channel = channels.find((c) => {
144
- // Direct name match
145
- if (c.name === channelNameOrId)
146
- return true;
147
- // Match without # prefix
148
- if (c.name === channelNameOrId.replace('#', ''))
149
- return true;
150
- // Case-insensitive match
151
- if (c.name?.toLowerCase() === channelNameOrId.toLowerCase())
152
- return true;
153
- // Match with normalized name
154
- if (c.name_normalized === channelNameOrId)
155
- return true;
156
- return false;
157
- });
158
- if (!channel) {
159
- // Provide helpful error message
160
- const similarChannels = channels
161
- .filter((c) => c.name?.toLowerCase().includes(channelNameOrId.toLowerCase()))
162
- .slice(0, 5)
163
- .map((c) => c.name);
164
- if (similarChannels.length > 0) {
165
- throw new Error(`Channel '${channelNameOrId}' not found. Did you mean one of these? ${similarChannels.join(', ')}`);
166
- }
167
- else {
168
- throw new Error(`Channel '${channelNameOrId}' not found. Make sure you are a member of this channel.`);
169
- }
170
- }
171
- channelId = channel.id;
172
- }
125
+ // Resolve channel name to ID if needed
126
+ const channelId = await channel_resolver_1.channelResolver.resolveChannelId(channelNameOrId, () => this.listChannels({
127
+ types: 'public_channel,private_channel,im,mpim',
128
+ exclude_archived: true,
129
+ limit: constants_1.DEFAULTS.CHANNELS_LIMIT,
130
+ }));
173
131
  // Get channel info with unread count
174
132
  const info = await this.client.conversations.info({
175
133
  channel: channelId,
@@ -1 +1 @@
1
- {"version":3,"file":"slack-api-client.js","sourceRoot":"","sources":["../../src/utils/slack-api-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"slack-api-client.js","sourceRoot":"","sources":["../../src/utils/slack-api-client.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@urugus/slack-cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "A command-line tool for sending messages to Slack",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -32,14 +32,15 @@
32
32
  "license": "MIT",
33
33
  "dependencies": {
34
34
  "@slack/web-api": "^6.9.0",
35
+ "chalk": "^4.1.2",
35
36
  "commander": "^11.1.0",
36
37
  "dotenv": "^16.3.1",
37
- "chalk": "^4.1.2",
38
- "inquirer": "^8.2.6"
38
+ "inquirer": "^8.2.6",
39
+ "p-limit": "^3.1.0"
39
40
  },
40
41
  "devDependencies": {
41
- "@types/node": "^20.10.0",
42
42
  "@types/inquirer": "^8.2.10",
43
+ "@types/node": "^20.10.0",
43
44
  "@typescript-eslint/eslint-plugin": "^6.13.0",
44
45
  "@typescript-eslint/parser": "^6.13.0",
45
46
  "@vitest/coverage-v8": "^1.0.4",
@@ -1,11 +1,13 @@
1
1
  import { Command } from 'commander';
2
2
  import { wrapCommand } from '../utils/command-wrapper';
3
3
  import { createSlackClient } from '../utils/client-factory';
4
- import { SlackApiClient, Channel } from '../utils/slack-api-client';
4
+ import { SlackApiClient } from '../utils/slack-api-client';
5
5
  import { UnreadOptions } from '../types/commands';
6
6
  import chalk from 'chalk';
7
7
  import { formatSlackTimestamp } from '../utils/date-utils';
8
8
  import { formatChannelName } from '../utils/channel-formatter';
9
+ import { createChannelFormatter } from '../utils/formatters/channel-formatters';
10
+ import { DEFAULTS } from '../utils/constants';
9
11
 
10
12
  async function handleSpecificChannelUnread(
11
13
  client: SlackApiClient,
@@ -40,58 +42,11 @@ async function handleAllChannelsUnread(
40
42
  }
41
43
 
42
44
  // Apply limit
43
- const limit = parseInt(options.limit || '50', 10);
45
+ const limit = parseInt(options.limit || DEFAULTS.UNREAD_DISPLAY_LIMIT.toString(), 10);
44
46
  const displayChannels = channels.slice(0, limit);
45
47
 
46
- if (options.countOnly) {
47
- displayCountOnly(displayChannels);
48
- } else if (options.format === 'json') {
49
- displayAsJson(displayChannels);
50
- } else if (options.format === 'simple') {
51
- displayAsSimple(displayChannels);
52
- } else {
53
- displayAsTable(displayChannels);
54
- }
55
- }
56
-
57
- function displayCountOnly(channels: Channel[]): void {
58
- let totalUnread = 0;
59
- channels.forEach((channel) => {
60
- const count = channel.unread_count || 0;
61
- totalUnread += count;
62
- const channelName = formatChannelName(channel.name);
63
- console.log(`${channelName}: ${count}`);
64
- });
65
- console.log(chalk.bold(`Total: ${totalUnread} unread messages`));
66
- }
67
-
68
- function displayAsJson(channels: Channel[]): void {
69
- const output = channels.map((channel) => ({
70
- channel: formatChannelName(channel.name),
71
- channelId: channel.id,
72
- unreadCount: channel.unread_count || 0,
73
- }));
74
- console.log(JSON.stringify(output, null, 2));
75
- }
76
-
77
- function displayAsSimple(channels: Channel[]): void {
78
- channels.forEach((channel) => {
79
- const channelName = formatChannelName(channel.name);
80
- console.log(`${channelName} (${channel.unread_count || 0})`);
81
- });
82
- }
83
-
84
- function displayAsTable(channels: Channel[]): void {
85
- console.log(chalk.bold('Channel Unread Last Message'));
86
- console.log('─'.repeat(50));
87
-
88
- channels.forEach((channel) => {
89
- const channelName = formatChannelName(channel.name);
90
- const paddedName = channelName.padEnd(16);
91
- const count = (channel.unread_count || 0).toString().padEnd(6);
92
- const lastRead = channel.last_read ? formatSlackTimestamp(channel.last_read) : 'Unknown';
93
- console.log(`${paddedName} ${count} ${lastRead}`);
94
- });
48
+ const formatter = createChannelFormatter(options.format || 'table', options.countOnly || false);
49
+ formatter.format(displayChannels);
95
50
  }
96
51
 
97
52
  export function setupUnreadCommand(): Command {
@@ -100,7 +55,11 @@ export function setupUnreadCommand(): Command {
100
55
  .option('-c, --channel <channel>', 'Show unread for a specific channel')
101
56
  .option('--format <format>', 'Output format: table, simple, json', 'table')
102
57
  .option('--count-only', 'Show only unread counts', false)
103
- .option('--limit <number>', 'Maximum number of channels to display', '50')
58
+ .option(
59
+ '--limit <number>',
60
+ 'Maximum number of channels to display',
61
+ DEFAULTS.UNREAD_DISPLAY_LIMIT.toString()
62
+ )
104
63
  .option('--profile <profile>', 'Use specific workspace profile')
105
64
  .action(
106
65
  wrapCommand(async (options: UnreadOptions) => {
package/src/index.ts CHANGED
@@ -5,10 +5,16 @@ import { setupSendCommand } from './commands/send';
5
5
  import { setupChannelsCommand } from './commands/channels';
6
6
  import { setupHistoryCommand } from './commands/history';
7
7
  import { setupUnreadCommand } from './commands/unread';
8
+ import { readFileSync } from 'fs';
9
+ import { join } from 'path';
8
10
 
9
11
  const program = new Command();
10
12
 
11
- program.name('slack-cli').description('CLI tool to send messages via Slack API').version('1.0.0');
13
+ // Read version from package.json
14
+ const packageJson = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
15
+ const version = packageJson.version;
16
+
17
+ program.name('slack-cli').description('CLI tool to send messages via Slack API').version(version);
12
18
 
13
19
  program.addCommand(setupConfigCommand());
14
20
  program.addCommand(setupSendCommand());
@@ -0,0 +1,86 @@
1
+ import { Channel } from './slack-api-client';
2
+
3
+ export type GetChannelsFunction = () => Promise<Channel[]>;
4
+
5
+ export class ChannelResolver {
6
+ /**
7
+ * Check if the given string is a channel ID
8
+ */
9
+ isChannelId(channelNameOrId: string): boolean {
10
+ return (
11
+ channelNameOrId.startsWith('C') ||
12
+ channelNameOrId.startsWith('D') ||
13
+ channelNameOrId.startsWith('G')
14
+ );
15
+ }
16
+
17
+ /**
18
+ * Find a channel by name from the given list
19
+ */
20
+ findChannel(channelName: string, channels: Channel[]): Channel | undefined {
21
+ return channels.find((c) => {
22
+ // Direct name match
23
+ if (c.name === channelName) return true;
24
+ // Match without # prefix
25
+ if (c.name === channelName.replace('#', '')) return true;
26
+ // Case-insensitive match
27
+ if (c.name?.toLowerCase() === channelName.toLowerCase()) return true;
28
+ // Match with normalized name
29
+ if (c.name_normalized === channelName) return true;
30
+ return false;
31
+ });
32
+ }
33
+
34
+ /**
35
+ * Get similar channel names for suggestions
36
+ */
37
+ getSimilarChannels(channelName: string, channels: Channel[], limit = 5): string[] {
38
+ return channels
39
+ .filter((c) => c.name?.toLowerCase().includes(channelName.toLowerCase()))
40
+ .slice(0, limit)
41
+ .map((c) => c.name as string);
42
+ }
43
+
44
+ /**
45
+ * Create an error with channel suggestions
46
+ */
47
+ resolveChannelError(channelName: string, channels: Channel[]): Error {
48
+ const similarChannels = this.getSimilarChannels(channelName, channels);
49
+
50
+ if (similarChannels.length > 0) {
51
+ return new Error(
52
+ `Channel '${channelName}' not found. Did you mean one of these? ${similarChannels.join(', ')}`
53
+ );
54
+ } else {
55
+ return new Error(
56
+ `Channel '${channelName}' not found. Make sure you are a member of this channel.`
57
+ );
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Resolve a channel name or ID to a channel ID
63
+ */
64
+ async resolveChannelId(
65
+ channelNameOrId: string,
66
+ getChannels: GetChannelsFunction
67
+ ): Promise<string> {
68
+ // If it's already an ID, return it
69
+ if (this.isChannelId(channelNameOrId)) {
70
+ return channelNameOrId;
71
+ }
72
+
73
+ // Otherwise, fetch channels and resolve the name
74
+ const channels = await getChannels();
75
+ const channel = this.findChannel(channelNameOrId, channels);
76
+
77
+ if (!channel) {
78
+ throw this.resolveChannelError(channelNameOrId, channels);
79
+ }
80
+
81
+ return channel.id;
82
+ }
83
+ }
84
+
85
+ // Export a singleton instance
86
+ export const channelResolver = new ChannelResolver();
@@ -45,3 +45,26 @@ export const API_LIMITS = {
45
45
  MIN_MESSAGE_COUNT: 1,
46
46
  DEFAULT_MESSAGE_COUNT: 10,
47
47
  };
48
+
49
+ // API Rate Limiting Configuration
50
+ export const RATE_LIMIT = {
51
+ CONCURRENT_REQUESTS: 3,
52
+ BATCH_SIZE: 10,
53
+ BATCH_DELAY_MS: 1000,
54
+ RETRY_CONFIG: {
55
+ retries: 3,
56
+ factor: 2,
57
+ minTimeout: 1000,
58
+ maxTimeout: 30000,
59
+ },
60
+ };
61
+
62
+ // Default values
63
+ export const DEFAULTS = {
64
+ HISTORY_LIMIT: 20,
65
+ CHANNELS_LIMIT: 1000,
66
+ UNREAD_DISPLAY_LIMIT: 50,
67
+ };
68
+
69
+ // Time formats
70
+ export const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';
@@ -0,0 +1,69 @@
1
+ import chalk from 'chalk';
2
+ import { BaseFormatter } from './output-formatter';
3
+ import { Channel } from '../slack-api-client';
4
+ import { formatChannelName } from '../channel-formatter';
5
+ import { formatSlackTimestamp } from '../date-utils';
6
+
7
+ export class ChannelTableFormatter extends BaseFormatter<Channel> {
8
+ format(channels: Channel[]): void {
9
+ console.log(chalk.bold('Channel Unread Last Message'));
10
+ console.log('─'.repeat(50));
11
+
12
+ channels.forEach((channel) => {
13
+ const channelName = formatChannelName(channel.name);
14
+ const paddedName = channelName.padEnd(16);
15
+ const count = (channel.unread_count || 0).toString().padEnd(6);
16
+ const lastRead = channel.last_read ? formatSlackTimestamp(channel.last_read) : 'Unknown';
17
+ console.log(`${paddedName} ${count} ${lastRead}`);
18
+ });
19
+ }
20
+ }
21
+
22
+ export class ChannelSimpleFormatter extends BaseFormatter<Channel> {
23
+ format(channels: Channel[]): void {
24
+ channels.forEach((channel) => {
25
+ const channelName = formatChannelName(channel.name);
26
+ console.log(`${channelName} (${channel.unread_count || 0})`);
27
+ });
28
+ }
29
+ }
30
+
31
+ export class ChannelJsonFormatter extends BaseFormatter<Channel> {
32
+ format(channels: Channel[]): void {
33
+ const output = channels.map((channel) => ({
34
+ channel: formatChannelName(channel.name),
35
+ channelId: channel.id,
36
+ unreadCount: channel.unread_count || 0,
37
+ }));
38
+ console.log(JSON.stringify(output, null, 2));
39
+ }
40
+ }
41
+
42
+ export class ChannelCountFormatter extends BaseFormatter<Channel> {
43
+ format(channels: Channel[]): void {
44
+ let totalUnread = 0;
45
+ channels.forEach((channel) => {
46
+ const count = channel.unread_count || 0;
47
+ totalUnread += count;
48
+ const channelName = formatChannelName(channel.name);
49
+ console.log(`${channelName}: ${count}`);
50
+ });
51
+ console.log(chalk.bold(`Total: ${totalUnread} unread messages`));
52
+ }
53
+ }
54
+
55
+ export function createChannelFormatter(format: string, countOnly: boolean): BaseFormatter<Channel> {
56
+ if (countOnly) {
57
+ return new ChannelCountFormatter();
58
+ }
59
+
60
+ switch (format) {
61
+ case 'json':
62
+ return new ChannelJsonFormatter();
63
+ case 'simple':
64
+ return new ChannelSimpleFormatter();
65
+ case 'table':
66
+ default:
67
+ return new ChannelTableFormatter();
68
+ }
69
+ }
@@ -0,0 +1,7 @@
1
+ export interface OutputFormatter<T> {
2
+ format(data: T[]): void;
3
+ }
4
+
5
+ export abstract class BaseFormatter<T> implements OutputFormatter<T> {
6
+ abstract format(data: T[]): void;
7
+ }
@@ -130,14 +130,16 @@ export class ProfileConfigManager {
130
130
  }
131
131
  }
132
132
 
133
- private needsMigration(data: any): boolean {
134
- return data.token && !data.profiles;
133
+ private needsMigration(data: unknown): boolean {
134
+ const configData = data as Record<string, unknown>;
135
+ return Boolean(configData.token && !configData.profiles);
135
136
  }
136
137
 
137
- private async migrateOldConfig(oldData: any): Promise<ConfigStore> {
138
+ private async migrateOldConfig(oldData: unknown): Promise<ConfigStore> {
139
+ const data = oldData as { token: string; updatedAt: string };
138
140
  const oldConfig: Config = {
139
- token: oldData.token,
140
- updatedAt: oldData.updatedAt,
141
+ token: data.token,
142
+ updatedAt: data.updatedAt,
141
143
  };
142
144
 
143
145
  const newStore: ConfigStore = {