@urugus/slack-cli 0.2.2 → 0.2.4
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/.claude/settings.local.json +2 -1
- package/dist/commands/channels.d.ts.map +1 -1
- package/dist/commands/channels.js +7 -4
- package/dist/commands/channels.js.map +1 -1
- package/dist/commands/unread.d.ts.map +1 -1
- package/dist/commands/unread.js +17 -21
- package/dist/commands/unread.js.map +1 -1
- package/dist/utils/config/config-file-manager.d.ts +13 -0
- package/dist/utils/config/config-file-manager.d.ts.map +1 -0
- package/dist/utils/config/config-file-manager.js +85 -0
- package/dist/utils/config/config-file-manager.js.map +1 -0
- package/dist/utils/config/profile-manager.d.ts +16 -0
- package/dist/utils/config/profile-manager.d.ts.map +1 -0
- package/dist/utils/config/profile-manager.js +64 -0
- package/dist/utils/config/profile-manager.js.map +1 -0
- package/dist/utils/config/token-crypto-service.d.ts +11 -0
- package/dist/utils/config/token-crypto-service.d.ts.map +1 -0
- package/dist/utils/config/token-crypto-service.js +111 -0
- package/dist/utils/config/token-crypto-service.js.map +1 -0
- package/dist/utils/format-utils.d.ts.map +1 -1
- package/dist/utils/format-utils.js +2 -1
- package/dist/utils/format-utils.js.map +1 -1
- package/dist/utils/formatters/base-formatter.d.ts +23 -0
- package/dist/utils/formatters/base-formatter.d.ts.map +1 -0
- package/dist/utils/formatters/base-formatter.js +26 -0
- package/dist/utils/formatters/base-formatter.js.map +1 -0
- package/dist/utils/formatters/channel-formatters.d.ts +4 -13
- package/dist/utils/formatters/channel-formatters.d.ts.map +1 -1
- package/dist/utils/formatters/channel-formatters.js +18 -26
- package/dist/utils/formatters/channel-formatters.js.map +1 -1
- package/dist/utils/formatters/channels-list-formatters.d.ts +3 -10
- package/dist/utils/formatters/channels-list-formatters.d.ts.map +1 -1
- package/dist/utils/formatters/channels-list-formatters.js +15 -22
- package/dist/utils/formatters/channels-list-formatters.js.map +1 -1
- package/dist/utils/formatters/message-formatters.d.ts +9 -0
- package/dist/utils/formatters/message-formatters.d.ts.map +1 -0
- package/dist/utils/formatters/message-formatters.js +72 -0
- package/dist/utils/formatters/message-formatters.js.map +1 -0
- package/dist/utils/mention-utils.d.ts +17 -0
- package/dist/utils/mention-utils.d.ts.map +1 -0
- package/dist/utils/mention-utils.js +45 -0
- package/dist/utils/mention-utils.js.map +1 -0
- package/dist/utils/option-parsers.d.ts +47 -0
- package/dist/utils/option-parsers.d.ts.map +1 -0
- package/dist/utils/option-parsers.js +75 -0
- package/dist/utils/option-parsers.js.map +1 -0
- package/dist/utils/profile-config-refactored.d.ts +20 -0
- package/dist/utils/profile-config-refactored.d.ts.map +1 -0
- package/dist/utils/profile-config-refactored.js +174 -0
- package/dist/utils/profile-config-refactored.js.map +1 -0
- package/dist/utils/slack-operations/message-operations.d.ts.map +1 -1
- package/dist/utils/slack-operations/message-operations.js +3 -2
- package/dist/utils/slack-operations/message-operations.js.map +1 -1
- package/dist/utils/slack-patterns.d.ts +6 -0
- package/dist/utils/slack-patterns.d.ts.map +1 -0
- package/dist/utils/slack-patterns.js +11 -0
- package/dist/utils/slack-patterns.js.map +1 -0
- package/package.json +1 -1
- package/src/commands/channels.ts +7 -4
- package/src/commands/unread.ts +18 -21
- package/src/utils/config/config-file-manager.ts +56 -0
- package/src/utils/config/profile-manager.ts +79 -0
- package/src/utils/config/token-crypto-service.ts +80 -0
- package/src/utils/format-utils.ts +3 -1
- package/src/utils/formatters/base-formatter.ts +34 -0
- package/src/utils/formatters/channel-formatters.ts +25 -23
- package/src/utils/formatters/channels-list-formatters.ts +27 -31
- package/src/utils/formatters/message-formatters.ts +85 -0
- package/src/utils/mention-utils.ts +47 -0
- package/src/utils/option-parsers.ts +100 -0
- package/src/utils/profile-config-refactored.ts +161 -0
- package/src/utils/slack-operations/message-operations.ts +3 -2
- package/src/utils/slack-patterns.ts +9 -0
- package/tests/commands/unread.test.ts +112 -0
- package/tests/utils/config/config-file-manager.test.ts +118 -0
- package/tests/utils/config/profile-manager.test.ts +266 -0
- package/tests/utils/config/token-crypto-service.test.ts +98 -0
- package/tests/utils/mention-utils.test.ts +100 -0
- package/tests/utils/slack-operations/message-operations.test.ts +126 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.profileConfig = exports.ProfileConfigManager = void 0;
|
|
37
|
+
const constants_1 = require("./constants");
|
|
38
|
+
const config_file_manager_1 = require("./config/config-file-manager");
|
|
39
|
+
const token_crypto_service_1 = require("./config/token-crypto-service");
|
|
40
|
+
const profile_manager_1 = require("./config/profile-manager");
|
|
41
|
+
const fs = __importStar(require("fs/promises"));
|
|
42
|
+
class ProfileConfigManager {
|
|
43
|
+
constructor(_options = {}) {
|
|
44
|
+
// Note: ConfigFileManager currently doesn't support custom configDir
|
|
45
|
+
// This would need to be added if required
|
|
46
|
+
this.fileManager = new config_file_manager_1.ConfigFileManager();
|
|
47
|
+
this.cryptoService = new token_crypto_service_1.TokenCryptoService();
|
|
48
|
+
this.profileManager = new profile_manager_1.ProfileManager(this.fileManager, this.cryptoService);
|
|
49
|
+
}
|
|
50
|
+
async setToken(token, profile) {
|
|
51
|
+
const profileName = profile || (await this.profileManager.getCurrentProfile());
|
|
52
|
+
const config = {
|
|
53
|
+
token,
|
|
54
|
+
updatedAt: new Date().toISOString(),
|
|
55
|
+
};
|
|
56
|
+
await this.profileManager.setProfile(profileName, config);
|
|
57
|
+
// Set as default profile if it's the first one or explicitly setting default
|
|
58
|
+
const profiles = await this.profileManager.listProfiles();
|
|
59
|
+
if (profiles.length === 1 || profileName === constants_1.DEFAULT_PROFILE_NAME) {
|
|
60
|
+
await this.profileManager.setCurrentProfile(profileName);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async getConfig(profile) {
|
|
64
|
+
const profileName = profile || (await this.profileManager.getCurrentProfile());
|
|
65
|
+
try {
|
|
66
|
+
return await this.profileManager.getProfile(profileName);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
// Return null if profile not found
|
|
70
|
+
if (error instanceof Error && error.message.includes('not found')) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async listProfiles() {
|
|
77
|
+
const profileNames = await this.profileManager.listProfiles();
|
|
78
|
+
const currentProfile = await this.profileManager.getCurrentProfile();
|
|
79
|
+
const profiles = [];
|
|
80
|
+
for (const name of profileNames) {
|
|
81
|
+
const config = await this.profileManager.getProfile(name);
|
|
82
|
+
profiles.push({
|
|
83
|
+
name,
|
|
84
|
+
config,
|
|
85
|
+
isDefault: name === currentProfile,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return profiles;
|
|
89
|
+
}
|
|
90
|
+
async useProfile(profile) {
|
|
91
|
+
const exists = await this.profileManager.profileExists(profile);
|
|
92
|
+
if (!exists) {
|
|
93
|
+
throw new Error(`Profile "${profile}" does not exist`);
|
|
94
|
+
}
|
|
95
|
+
await this.profileManager.setCurrentProfile(profile);
|
|
96
|
+
}
|
|
97
|
+
async getCurrentProfile() {
|
|
98
|
+
return await this.profileManager.getCurrentProfile();
|
|
99
|
+
}
|
|
100
|
+
async clearConfig(profile) {
|
|
101
|
+
const profileName = profile || (await this.profileManager.getCurrentProfile());
|
|
102
|
+
try {
|
|
103
|
+
await this.profileManager.deleteProfile(profileName);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
// If profile doesn't exist, do nothing
|
|
107
|
+
if (error instanceof Error && error.message.includes('not found')) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
// If we deleted the current profile, set a new default
|
|
113
|
+
const currentProfile = await this.profileManager.getCurrentProfile();
|
|
114
|
+
if (currentProfile === profileName) {
|
|
115
|
+
const remainingProfiles = await this.profileManager.listProfiles();
|
|
116
|
+
if (remainingProfiles.length > 0) {
|
|
117
|
+
await this.profileManager.setCurrentProfile(remainingProfiles[0]);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
// No profiles left, delete the config file
|
|
121
|
+
try {
|
|
122
|
+
await fs.unlink(this.fileManager.getConfigPath());
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
if (error && typeof error === 'object' && 'code' in error && error.code !== 'ENOENT') {
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
maskToken(token) {
|
|
133
|
+
if (token.length <= constants_1.TOKEN_MIN_LENGTH) {
|
|
134
|
+
return '****';
|
|
135
|
+
}
|
|
136
|
+
const prefix = token.substring(0, constants_1.TOKEN_MASK_LENGTH);
|
|
137
|
+
const suffix = token.substring(token.length - constants_1.TOKEN_MASK_LENGTH);
|
|
138
|
+
return `${prefix}-****-****-${suffix}`;
|
|
139
|
+
}
|
|
140
|
+
// Migration support - to be called separately if needed
|
|
141
|
+
async migrateIfNeeded() {
|
|
142
|
+
const data = await this.fileManager.read();
|
|
143
|
+
// Check if migration is needed (old format detection)
|
|
144
|
+
const anyData = data;
|
|
145
|
+
if (anyData.token && !anyData.profiles) {
|
|
146
|
+
// Old format detected, migrate
|
|
147
|
+
const oldConfig = {
|
|
148
|
+
token: anyData.token,
|
|
149
|
+
updatedAt: anyData.updatedAt || new Date().toISOString(),
|
|
150
|
+
};
|
|
151
|
+
// Create new format
|
|
152
|
+
const newData = {
|
|
153
|
+
profiles: { [constants_1.DEFAULT_PROFILE_NAME]: oldConfig },
|
|
154
|
+
currentProfile: constants_1.DEFAULT_PROFILE_NAME,
|
|
155
|
+
};
|
|
156
|
+
await this.fileManager.write(newData);
|
|
157
|
+
// Re-encrypt token using new service
|
|
158
|
+
await this.setToken(oldConfig.token, constants_1.DEFAULT_PROFILE_NAME);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
exports.ProfileConfigManager = ProfileConfigManager;
|
|
163
|
+
// Export a simplified version for backward compatibility
|
|
164
|
+
exports.profileConfig = {
|
|
165
|
+
getCurrentProfile: () => {
|
|
166
|
+
return constants_1.DEFAULT_PROFILE_NAME;
|
|
167
|
+
},
|
|
168
|
+
getToken: (_profile) => {
|
|
169
|
+
// This is a simplified version for testing
|
|
170
|
+
// In real usage, it would need to be async
|
|
171
|
+
return undefined;
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
//# sourceMappingURL=profile-config-refactored.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile-config-refactored.js","sourceRoot":"","sources":["../../src/utils/profile-config-refactored.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message-operations.d.ts","sourceRoot":"","sources":["../../../src/utils/slack-operations/message-operations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGhD,OAAO,EAAW,cAAc,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"message-operations.d.ts","sourceRoot":"","sources":["../../../src/utils/slack-operations/message-operations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGhD,OAAO,EAAW,cAAc,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAIlG,qBAAa,iBAAkB,SAAQ,eAAe;IACpD,OAAO,CAAC,UAAU,CAAoB;gBAE1B,KAAK,EAAE,MAAM;IAKnB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC;IAO5E,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IAyB5E,gBAAgB,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;YAsC/D,aAAa;CAmB5B"}
|
|
@@ -5,6 +5,7 @@ const base_client_1 = require("./base-client");
|
|
|
5
5
|
const channel_resolver_1 = require("../channel-resolver");
|
|
6
6
|
const constants_1 = require("../constants");
|
|
7
7
|
const channel_operations_1 = require("./channel-operations");
|
|
8
|
+
const mention_utils_1 = require("../mention-utils");
|
|
8
9
|
class MessageOperations extends base_client_1.BaseSlackClient {
|
|
9
10
|
constructor(token) {
|
|
10
11
|
super(token);
|
|
@@ -29,8 +30,8 @@ class MessageOperations extends base_client_1.BaseSlackClient {
|
|
|
29
30
|
oldest: options.oldest,
|
|
30
31
|
});
|
|
31
32
|
const messages = response.messages;
|
|
32
|
-
//
|
|
33
|
-
const userIds =
|
|
33
|
+
// Extract all unique user IDs (authors and mentioned users)
|
|
34
|
+
const userIds = (0, mention_utils_1.extractAllUserIds)(messages);
|
|
34
35
|
const users = await this.fetchUserInfo(userIds);
|
|
35
36
|
return { messages, users };
|
|
36
37
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message-operations.js","sourceRoot":"","sources":["../../../src/utils/slack-operations/message-operations.ts"],"names":[],"mappings":";;;AACA,+CAAgD;AAChD,0DAAsD;AACtD,4CAAwC;AAExC,6DAAyD;
|
|
1
|
+
{"version":3,"file":"message-operations.js","sourceRoot":"","sources":["../../../src/utils/slack-operations/message-operations.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack-patterns.d.ts","sourceRoot":"","sources":["../../src/utils/slack-patterns.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,eAAO,MAAM,oBAAoB,QAAoB,CAAC;AAGtD,eAAO,MAAM,2BAA2B,QAAmB,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Common regex patterns for Slack message parsing
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SINGLE_USER_MENTION_PATTERN = exports.USER_MENTION_PATTERN = void 0;
|
|
7
|
+
// Matches Slack user mentions in the format <@USERID>
|
|
8
|
+
exports.USER_MENTION_PATTERN = /<@([A-Z0-9]+)>/g;
|
|
9
|
+
// Matches a single user mention (non-global)
|
|
10
|
+
exports.SINGLE_USER_MENTION_PATTERN = /<@([A-Z0-9]+)>/;
|
|
11
|
+
//# sourceMappingURL=slack-patterns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack-patterns.js","sourceRoot":"","sources":["../../src/utils/slack-patterns.ts"],"names":[],"mappings":";AAAA;;GAEG;;;AAEH,sDAAsD;AACzC,QAAA,oBAAoB,GAAG,iBAAiB,CAAC;AAEtD,6CAA6C;AAChC,QAAA,2BAA2B,GAAG,gBAAgB,CAAC"}
|
package/package.json
CHANGED
package/src/commands/channels.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { ERROR_MESSAGES } from '../utils/constants';
|
|
|
5
5
|
import { ChannelsOptions } from '../types/commands';
|
|
6
6
|
import { mapChannelToInfo, getChannelTypes } from '../utils/channel-formatter';
|
|
7
7
|
import { createChannelsListFormatter } from '../utils/formatters/channels-list-formatters';
|
|
8
|
+
import { parseFormat, parseLimit, parseBoolean } from '../utils/option-parsers';
|
|
8
9
|
|
|
9
10
|
export function setupChannelsCommand(): Command {
|
|
10
11
|
const channelsCommand = new Command('channels');
|
|
@@ -25,10 +26,11 @@ export function setupChannelsCommand(): Command {
|
|
|
25
26
|
const types = getChannelTypes(options.type);
|
|
26
27
|
|
|
27
28
|
// List channels
|
|
29
|
+
const limit = parseLimit(options.limit, 100);
|
|
28
30
|
const channels = await client.listChannels({
|
|
29
31
|
types,
|
|
30
|
-
exclude_archived: !options.includeArchived,
|
|
31
|
-
limit:
|
|
32
|
+
exclude_archived: !parseBoolean(options.includeArchived),
|
|
33
|
+
limit: limit,
|
|
32
34
|
});
|
|
33
35
|
|
|
34
36
|
if (channels.length === 0) {
|
|
@@ -38,8 +40,9 @@ export function setupChannelsCommand(): Command {
|
|
|
38
40
|
|
|
39
41
|
// Format and display channels
|
|
40
42
|
const channelInfos = channels.map(mapChannelToInfo);
|
|
41
|
-
const
|
|
42
|
-
formatter
|
|
43
|
+
const format = parseFormat(options.format);
|
|
44
|
+
const formatter = createChannelsListFormatter(format);
|
|
45
|
+
formatter.format({ channels: channelInfos });
|
|
43
46
|
})
|
|
44
47
|
);
|
|
45
48
|
|
package/src/commands/unread.ts
CHANGED
|
@@ -4,34 +4,28 @@ import { createSlackClient } from '../utils/client-factory';
|
|
|
4
4
|
import { SlackApiClient } from '../utils/slack-api-client';
|
|
5
5
|
import { UnreadOptions } from '../types/commands';
|
|
6
6
|
import chalk from 'chalk';
|
|
7
|
-
import { formatSlackTimestamp } from '../utils/date-utils';
|
|
8
|
-
import { formatChannelName } from '../utils/channel-formatter';
|
|
9
7
|
import { createChannelFormatter } from '../utils/formatters/channel-formatters';
|
|
8
|
+
import { createMessageFormatter } from '../utils/formatters/message-formatters';
|
|
10
9
|
import { DEFAULTS } from '../utils/constants';
|
|
11
|
-
import {
|
|
10
|
+
import { parseLimit, parseFormat, parseBoolean } from '../utils/option-parsers';
|
|
12
11
|
|
|
13
12
|
async function handleSpecificChannelUnread(
|
|
14
13
|
client: SlackApiClient,
|
|
15
14
|
options: UnreadOptions
|
|
16
15
|
): Promise<void> {
|
|
17
16
|
const result = await client.getChannelUnread(options.channel!);
|
|
18
|
-
const channelName = formatChannelName(result.channel.name);
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
const format = parseFormat(options.format);
|
|
19
|
+
const countOnly = parseBoolean(options.countOnly);
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
result.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
: '(no text)';
|
|
31
|
-
console.log(text);
|
|
32
|
-
console.log('');
|
|
33
|
-
});
|
|
34
|
-
}
|
|
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
|
+
});
|
|
35
29
|
}
|
|
36
30
|
|
|
37
31
|
async function handleAllChannelsUnread(
|
|
@@ -46,11 +40,14 @@ async function handleAllChannelsUnread(
|
|
|
46
40
|
}
|
|
47
41
|
|
|
48
42
|
// Apply limit
|
|
49
|
-
const limit =
|
|
43
|
+
const limit = parseLimit(options.limit, DEFAULTS.UNREAD_DISPLAY_LIMIT);
|
|
50
44
|
const displayChannels = channels.slice(0, limit);
|
|
51
45
|
|
|
52
|
-
const
|
|
53
|
-
|
|
46
|
+
const format = parseFormat(options.format);
|
|
47
|
+
const countOnly = parseBoolean(options.countOnly);
|
|
48
|
+
|
|
49
|
+
const formatter = createChannelFormatter(format, countOnly);
|
|
50
|
+
formatter.format({ channels: displayChannels, countOnly: countOnly });
|
|
54
51
|
}
|
|
55
52
|
|
|
56
53
|
export function setupUnreadCommand(): Command {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import * as os from 'os';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
|
|
5
|
+
export interface ConfigData {
|
|
6
|
+
profiles: Record<string, any>;
|
|
7
|
+
currentProfile: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class ConfigFileManager {
|
|
11
|
+
private readonly configPath: string;
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
this.configPath = path.join(os.homedir(), '.slack-cli', 'config.json');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async read(): Promise<ConfigData> {
|
|
18
|
+
try {
|
|
19
|
+
await fs.access(this.configPath);
|
|
20
|
+
const data = await fs.readFile(this.configPath, 'utf-8');
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(data);
|
|
23
|
+
} catch {
|
|
24
|
+
throw new Error('Invalid configuration file');
|
|
25
|
+
}
|
|
26
|
+
} catch (error: any) {
|
|
27
|
+
if (error.message === 'Invalid configuration file') {
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
// File doesn't exist, return default config
|
|
31
|
+
return {
|
|
32
|
+
profiles: {},
|
|
33
|
+
currentProfile: 'default',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async write(data: ConfigData): Promise<void> {
|
|
39
|
+
const dir = path.dirname(this.configPath);
|
|
40
|
+
await fs.mkdir(dir, { recursive: true });
|
|
41
|
+
await fs.writeFile(this.configPath, JSON.stringify(data, null, 2), 'utf-8');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async exists(): Promise<boolean> {
|
|
45
|
+
try {
|
|
46
|
+
await fs.access(this.configPath);
|
|
47
|
+
return true;
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
getConfigPath(): string {
|
|
54
|
+
return this.configPath;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Config } from '../../types/config';
|
|
2
|
+
import { ConfigFileManager, ConfigData } from './config-file-manager';
|
|
3
|
+
import { TokenCryptoService } from './token-crypto-service';
|
|
4
|
+
|
|
5
|
+
export class ProfileManager {
|
|
6
|
+
constructor(
|
|
7
|
+
private fileManager: ConfigFileManager,
|
|
8
|
+
private cryptoService: TokenCryptoService
|
|
9
|
+
) {}
|
|
10
|
+
|
|
11
|
+
async getProfile(profileName: string): Promise<Config> {
|
|
12
|
+
const data = await this.fileManager.read();
|
|
13
|
+
const profile = data.profiles[profileName];
|
|
14
|
+
|
|
15
|
+
if (!profile) {
|
|
16
|
+
throw new Error(`Profile "${profileName}" not found`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Decrypt token if encrypted
|
|
20
|
+
const token = this.cryptoService.isEncrypted(profile.token)
|
|
21
|
+
? this.cryptoService.decrypt(profile.token)
|
|
22
|
+
: profile.token;
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
...profile,
|
|
26
|
+
token,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async setProfile(profileName: string, config: Config): Promise<void> {
|
|
31
|
+
const data = await this.fileManager.read();
|
|
32
|
+
|
|
33
|
+
// Encrypt the token before saving
|
|
34
|
+
const encryptedConfig = {
|
|
35
|
+
...config,
|
|
36
|
+
token: this.cryptoService.encrypt(config.token),
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
data.profiles[profileName] = encryptedConfig;
|
|
40
|
+
await this.fileManager.write(data);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async deleteProfile(profileName: string): Promise<void> {
|
|
44
|
+
const data = await this.fileManager.read();
|
|
45
|
+
|
|
46
|
+
if (!data.profiles[profileName]) {
|
|
47
|
+
throw new Error(`Profile "${profileName}" not found`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
delete data.profiles[profileName];
|
|
51
|
+
await this.fileManager.write(data);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async listProfiles(): Promise<string[]> {
|
|
55
|
+
const data = await this.fileManager.read();
|
|
56
|
+
return Object.keys(data.profiles);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async getCurrentProfile(): Promise<string> {
|
|
60
|
+
const data = await this.fileManager.read();
|
|
61
|
+
return data.currentProfile || 'default';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async setCurrentProfile(profileName: string): Promise<void> {
|
|
65
|
+
const data = await this.fileManager.read();
|
|
66
|
+
|
|
67
|
+
if (!data.profiles[profileName]) {
|
|
68
|
+
throw new Error(`Profile "${profileName}" not found`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
data.currentProfile = profileName;
|
|
72
|
+
await this.fileManager.write(data);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async profileExists(profileName: string): Promise<boolean> {
|
|
76
|
+
const data = await this.fileManager.read();
|
|
77
|
+
return profileName in data.profiles;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import * as crypto from 'crypto';
|
|
2
|
+
|
|
3
|
+
export class TokenCryptoService {
|
|
4
|
+
private readonly algorithm = 'aes-256-cbc';
|
|
5
|
+
private readonly keyLength = 32;
|
|
6
|
+
private readonly ivLength = 16;
|
|
7
|
+
private readonly separator = ':';
|
|
8
|
+
|
|
9
|
+
private deriveKey(): Buffer {
|
|
10
|
+
// Derive a consistent key from a fixed string
|
|
11
|
+
// In production, this should use a more secure method
|
|
12
|
+
const fixedSalt = 'slack-cli-salt-v1';
|
|
13
|
+
return crypto.pbkdf2Sync('slack-cli-key', fixedSalt, 100000, this.keyLength, 'sha256');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
encrypt(token: string): string {
|
|
17
|
+
try {
|
|
18
|
+
const key = this.deriveKey();
|
|
19
|
+
const iv = crypto.randomBytes(this.ivLength);
|
|
20
|
+
const cipher = crypto.createCipheriv(this.algorithm, key, iv);
|
|
21
|
+
|
|
22
|
+
let encrypted = cipher.update(token, 'utf8', 'hex');
|
|
23
|
+
encrypted += cipher.final('hex');
|
|
24
|
+
|
|
25
|
+
// Combine IV and encrypted data
|
|
26
|
+
return iv.toString('hex') + this.separator + encrypted;
|
|
27
|
+
} catch (error) {
|
|
28
|
+
throw new Error('Failed to encrypt token');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
decrypt(encryptedData: string): string {
|
|
33
|
+
try {
|
|
34
|
+
if (!encryptedData || !encryptedData.includes(this.separator)) {
|
|
35
|
+
throw new Error('Invalid encrypted data format');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const parts = encryptedData.split(this.separator);
|
|
39
|
+
if (parts.length !== 2) {
|
|
40
|
+
throw new Error('Invalid encrypted data format');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const iv = Buffer.from(parts[0], 'hex');
|
|
44
|
+
const encrypted = parts[1];
|
|
45
|
+
|
|
46
|
+
if (iv.length !== this.ivLength) {
|
|
47
|
+
throw new Error('Invalid IV length');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const key = this.deriveKey();
|
|
51
|
+
const decipher = crypto.createDecipheriv(this.algorithm, key, iv);
|
|
52
|
+
|
|
53
|
+
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
54
|
+
decrypted += decipher.final('utf8');
|
|
55
|
+
|
|
56
|
+
return decrypted;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
throw new Error('Failed to decrypt token');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
isEncrypted(value: string): boolean {
|
|
63
|
+
if (!value) return false;
|
|
64
|
+
|
|
65
|
+
// Check if the value has the expected format
|
|
66
|
+
const parts = value.split(this.separator);
|
|
67
|
+
if (parts.length !== 2) return false;
|
|
68
|
+
|
|
69
|
+
// Check if the IV part is valid hex and has correct length
|
|
70
|
+
try {
|
|
71
|
+
const ivHex = parts[0];
|
|
72
|
+
if (!/^[0-9a-fA-F]+$/.test(ivHex)) return false;
|
|
73
|
+
if (ivHex.length !== this.ivLength * 2) return false;
|
|
74
|
+
|
|
75
|
+
return true;
|
|
76
|
+
} catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { USER_MENTION_PATTERN } from './slack-patterns';
|
|
2
|
+
|
|
1
3
|
export function formatMessageWithMentions(message: string, users: Map<string, string>): string {
|
|
2
4
|
// Replace <@USERID> mentions with @username
|
|
3
|
-
return message.replace(
|
|
5
|
+
return message.replace(USER_MENTION_PATTERN, (match, userId) => {
|
|
4
6
|
const username = users.get(userId) || userId;
|
|
5
7
|
return `@${username}`;
|
|
6
8
|
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface BaseFormatter<T> {
|
|
2
|
+
format(data: T): void;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export abstract class AbstractFormatter<T> implements BaseFormatter<T> {
|
|
6
|
+
abstract format(data: T): void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export abstract class JsonFormatter<TInput, TOutput = any> extends AbstractFormatter<TInput> {
|
|
10
|
+
protected abstract transform(data: TInput): TOutput;
|
|
11
|
+
|
|
12
|
+
format(data: TInput): void {
|
|
13
|
+
console.log(JSON.stringify(this.transform(data), null, 2));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface FormatterMap<T> {
|
|
18
|
+
table: BaseFormatter<T>;
|
|
19
|
+
simple: BaseFormatter<T>;
|
|
20
|
+
json: BaseFormatter<T>;
|
|
21
|
+
[key: string]: BaseFormatter<T>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class FormatterFactory<T> {
|
|
25
|
+
constructor(private formatters: FormatterMap<T>) {}
|
|
26
|
+
|
|
27
|
+
create(format: string = 'table'): BaseFormatter<T> {
|
|
28
|
+
return this.formatters[format] || this.formatters.table;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function createFormatterFactory<T>(formatters: FormatterMap<T>): FormatterFactory<T> {
|
|
33
|
+
return new FormatterFactory(formatters);
|
|
34
|
+
}
|