@hasna/connectors 0.3.16 → 0.4.0

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 (127) hide show
  1. package/bin/index.js +71 -1
  2. package/bin/mcp.js +71 -1
  3. package/bin/serve.js +70 -0
  4. package/connectors/connect-asana/.env.example +11 -0
  5. package/connectors/connect-asana/CLAUDE.md +128 -0
  6. package/connectors/connect-asana/README.md +193 -0
  7. package/connectors/connect-asana/package.json +52 -0
  8. package/connectors/connect-asana/src/api/client.ts +119 -0
  9. package/connectors/connect-asana/src/api/index.ts +319 -0
  10. package/connectors/connect-asana/src/cli/index.ts +731 -0
  11. package/connectors/connect-asana/src/index.ts +19 -0
  12. package/connectors/connect-asana/src/types/index.ts +270 -0
  13. package/connectors/connect-asana/src/utils/config.ts +171 -0
  14. package/connectors/connect-asana/src/utils/output.ts +119 -0
  15. package/connectors/connect-asana/tsconfig.json +16 -0
  16. package/connectors/connect-clickup/.env.example +11 -0
  17. package/connectors/connect-clickup/CLAUDE.md +128 -0
  18. package/connectors/connect-clickup/README.md +193 -0
  19. package/connectors/connect-clickup/package.json +52 -0
  20. package/connectors/connect-clickup/src/api/client.ts +116 -0
  21. package/connectors/connect-clickup/src/api/index.ts +400 -0
  22. package/connectors/connect-clickup/src/cli/index.ts +625 -0
  23. package/connectors/connect-clickup/src/index.ts +19 -0
  24. package/connectors/connect-clickup/src/types/index.ts +591 -0
  25. package/connectors/connect-clickup/src/utils/config.ts +157 -0
  26. package/connectors/connect-clickup/src/utils/output.ts +119 -0
  27. package/connectors/connect-clickup/tsconfig.json +16 -0
  28. package/connectors/connect-confluence/.env.example +11 -0
  29. package/connectors/connect-confluence/CLAUDE.md +272 -0
  30. package/connectors/connect-confluence/README.md +193 -0
  31. package/connectors/connect-confluence/package.json +53 -0
  32. package/connectors/connect-confluence/scripts/release.ts +179 -0
  33. package/connectors/connect-confluence/src/api/client.ts +213 -0
  34. package/connectors/connect-confluence/src/api/example.ts +48 -0
  35. package/connectors/connect-confluence/src/api/index.ts +51 -0
  36. package/connectors/connect-confluence/src/cli/index.ts +254 -0
  37. package/connectors/connect-confluence/src/index.ts +103 -0
  38. package/connectors/connect-confluence/src/types/index.ts +237 -0
  39. package/connectors/connect-confluence/src/utils/auth.ts +274 -0
  40. package/connectors/connect-confluence/src/utils/bulk.ts +212 -0
  41. package/connectors/connect-confluence/src/utils/config.ts +326 -0
  42. package/connectors/connect-confluence/src/utils/output.ts +175 -0
  43. package/connectors/connect-confluence/src/utils/settings.ts +114 -0
  44. package/connectors/connect-confluence/src/utils/storage.ts +198 -0
  45. package/connectors/connect-confluence/tsconfig.json +16 -0
  46. package/connectors/connect-jira/.env.example +11 -0
  47. package/connectors/connect-jira/CLAUDE.md +128 -0
  48. package/connectors/connect-jira/README.md +193 -0
  49. package/connectors/connect-jira/package.json +53 -0
  50. package/connectors/connect-jira/src/api/client.ts +131 -0
  51. package/connectors/connect-jira/src/api/index.ts +266 -0
  52. package/connectors/connect-jira/src/cli/index.ts +653 -0
  53. package/connectors/connect-jira/src/index.ts +23 -0
  54. package/connectors/connect-jira/src/types/index.ts +448 -0
  55. package/connectors/connect-jira/src/utils/config.ts +179 -0
  56. package/connectors/connect-jira/src/utils/output.ts +119 -0
  57. package/connectors/connect-jira/tsconfig.json +16 -0
  58. package/connectors/connect-linear/CLAUDE.md +88 -0
  59. package/connectors/connect-linear/README.md +201 -0
  60. package/connectors/connect-linear/package.json +45 -0
  61. package/connectors/connect-linear/src/api/client.ts +62 -0
  62. package/connectors/connect-linear/src/api/index.ts +46 -0
  63. package/connectors/connect-linear/src/api/issues.ts +247 -0
  64. package/connectors/connect-linear/src/api/projects.ts +179 -0
  65. package/connectors/connect-linear/src/api/teams.ts +125 -0
  66. package/connectors/connect-linear/src/api/users.ts +112 -0
  67. package/connectors/connect-linear/src/cli/index.ts +560 -0
  68. package/connectors/connect-linear/src/index.ts +27 -0
  69. package/connectors/connect-linear/src/types/index.ts +275 -0
  70. package/connectors/connect-linear/src/utils/config.ts +249 -0
  71. package/connectors/connect-linear/src/utils/output.ts +119 -0
  72. package/connectors/connect-linear/tsconfig.json +16 -0
  73. package/connectors/connect-slack/.env.example +7 -0
  74. package/connectors/connect-slack/CLAUDE.md +69 -0
  75. package/connectors/connect-slack/README.md +150 -0
  76. package/connectors/connect-slack/package.json +44 -0
  77. package/connectors/connect-slack/src/api/channels.ts +112 -0
  78. package/connectors/connect-slack/src/api/client.ts +97 -0
  79. package/connectors/connect-slack/src/api/index.ts +42 -0
  80. package/connectors/connect-slack/src/api/messages.ts +127 -0
  81. package/connectors/connect-slack/src/api/users.ts +110 -0
  82. package/connectors/connect-slack/src/cli/index.ts +494 -0
  83. package/connectors/connect-slack/src/index.ts +21 -0
  84. package/connectors/connect-slack/src/types/index.ts +263 -0
  85. package/connectors/connect-slack/src/utils/config.ts +297 -0
  86. package/connectors/connect-slack/src/utils/output.ts +119 -0
  87. package/connectors/connect-slack/tsconfig.json +16 -0
  88. package/connectors/connect-telegram/.env.example +2 -0
  89. package/connectors/connect-telegram/package.json +49 -0
  90. package/connectors/connect-todoist/.env.example +11 -0
  91. package/connectors/connect-todoist/CLAUDE.md +104 -0
  92. package/connectors/connect-todoist/README.md +193 -0
  93. package/connectors/connect-todoist/package.json +52 -0
  94. package/connectors/connect-todoist/src/api/client.ts +117 -0
  95. package/connectors/connect-todoist/src/api/index.ts +188 -0
  96. package/connectors/connect-todoist/src/cli/index.ts +990 -0
  97. package/connectors/connect-todoist/src/index.ts +21 -0
  98. package/connectors/connect-todoist/src/types/index.ts +240 -0
  99. package/connectors/connect-todoist/src/utils/config.ts +157 -0
  100. package/connectors/connect-todoist/src/utils/output.ts +119 -0
  101. package/connectors/connect-todoist/tsconfig.json +16 -0
  102. package/connectors/connect-trello/.env.example +11 -0
  103. package/connectors/connect-trello/CLAUDE.md +128 -0
  104. package/connectors/connect-trello/README.md +193 -0
  105. package/connectors/connect-trello/package.json +53 -0
  106. package/connectors/connect-trello/src/api/client.ts +128 -0
  107. package/connectors/connect-trello/src/api/index.ts +278 -0
  108. package/connectors/connect-trello/src/cli/index.ts +737 -0
  109. package/connectors/connect-trello/src/index.ts +21 -0
  110. package/connectors/connect-trello/src/types/index.ts +314 -0
  111. package/connectors/connect-trello/src/utils/config.ts +182 -0
  112. package/connectors/connect-trello/src/utils/output.ts +119 -0
  113. package/connectors/connect-trello/tsconfig.json +16 -0
  114. package/connectors/connect-whatsapp/.env.example +11 -0
  115. package/connectors/connect-whatsapp/CLAUDE.md +113 -0
  116. package/connectors/connect-whatsapp/README.md +193 -0
  117. package/connectors/connect-whatsapp/package.json +53 -0
  118. package/connectors/connect-whatsapp/src/api/client.ts +133 -0
  119. package/connectors/connect-whatsapp/src/api/index.ts +365 -0
  120. package/connectors/connect-whatsapp/src/cli/index.ts +686 -0
  121. package/connectors/connect-whatsapp/src/index.ts +25 -0
  122. package/connectors/connect-whatsapp/src/types/index.ts +502 -0
  123. package/connectors/connect-whatsapp/src/utils/config.ts +179 -0
  124. package/connectors/connect-whatsapp/src/utils/output.ts +119 -0
  125. package/connectors/connect-whatsapp/tsconfig.json +16 -0
  126. package/dist/index.js +70 -0
  127. package/package.json +1 -1
@@ -0,0 +1,263 @@
1
+ // Slack API Types
2
+
3
+ export type OutputFormat = 'json' | 'table' | 'pretty';
4
+
5
+ // Configuration
6
+ export interface SlackConfig {
7
+ accessToken?: string;
8
+ botToken?: string;
9
+ teamId?: string;
10
+ baseUrl?: string;
11
+ }
12
+
13
+ export interface CliConfig {
14
+ botToken?: string;
15
+ userToken?: string;
16
+ teamId?: string;
17
+ teamName?: string;
18
+ defaultChannel?: string;
19
+ }
20
+
21
+ export interface OAuth2Tokens {
22
+ accessToken: string;
23
+ tokenType: string;
24
+ scope: string;
25
+ botUserId?: string;
26
+ appId?: string;
27
+ team?: {
28
+ id: string;
29
+ name: string;
30
+ };
31
+ authedUser?: {
32
+ id: string;
33
+ scope: string;
34
+ accessToken: string;
35
+ tokenType: string;
36
+ };
37
+ expiresAt?: number;
38
+ }
39
+
40
+ // API Error
41
+ export class SlackApiError extends Error {
42
+ constructor(
43
+ message: string,
44
+ public readonly code: string,
45
+ public readonly statusCode?: number
46
+ ) {
47
+ super(message);
48
+ this.name = 'SlackApiError';
49
+ }
50
+ }
51
+
52
+ // User types
53
+ export interface SlackUser {
54
+ id: string;
55
+ team_id: string;
56
+ name: string;
57
+ deleted: boolean;
58
+ real_name?: string;
59
+ tz?: string;
60
+ tz_label?: string;
61
+ tz_offset?: number;
62
+ profile: SlackUserProfile;
63
+ is_admin?: boolean;
64
+ is_owner?: boolean;
65
+ is_primary_owner?: boolean;
66
+ is_restricted?: boolean;
67
+ is_ultra_restricted?: boolean;
68
+ is_bot?: boolean;
69
+ is_app_user?: boolean;
70
+ updated?: number;
71
+ }
72
+
73
+ export interface SlackUserProfile {
74
+ title?: string;
75
+ phone?: string;
76
+ skype?: string;
77
+ real_name?: string;
78
+ real_name_normalized?: string;
79
+ display_name?: string;
80
+ display_name_normalized?: string;
81
+ status_text?: string;
82
+ status_emoji?: string;
83
+ status_expiration?: number;
84
+ avatar_hash?: string;
85
+ email?: string;
86
+ image_24?: string;
87
+ image_32?: string;
88
+ image_48?: string;
89
+ image_72?: string;
90
+ image_192?: string;
91
+ image_512?: string;
92
+ team?: string;
93
+ }
94
+
95
+ // Channel types
96
+ export interface SlackChannel {
97
+ id: string;
98
+ name: string;
99
+ is_channel?: boolean;
100
+ is_group?: boolean;
101
+ is_im?: boolean;
102
+ is_mpim?: boolean;
103
+ is_private?: boolean;
104
+ created?: number;
105
+ is_archived?: boolean;
106
+ is_general?: boolean;
107
+ unlinked?: number;
108
+ name_normalized?: string;
109
+ is_shared?: boolean;
110
+ is_org_shared?: boolean;
111
+ is_pending_ext_shared?: boolean;
112
+ pending_shared?: string[];
113
+ context_team_id?: string;
114
+ updated?: number;
115
+ creator?: string;
116
+ is_member?: boolean;
117
+ num_members?: number;
118
+ topic?: {
119
+ value: string;
120
+ creator: string;
121
+ last_set: number;
122
+ };
123
+ purpose?: {
124
+ value: string;
125
+ creator: string;
126
+ last_set: number;
127
+ };
128
+ }
129
+
130
+ // Message types
131
+ export interface SlackMessage {
132
+ type: string;
133
+ subtype?: string;
134
+ ts: string;
135
+ user?: string;
136
+ text: string;
137
+ bot_id?: string;
138
+ app_id?: string;
139
+ team?: string;
140
+ blocks?: SlackBlock[];
141
+ attachments?: SlackAttachment[];
142
+ edited?: {
143
+ user: string;
144
+ ts: string;
145
+ };
146
+ reply_count?: number;
147
+ reply_users_count?: number;
148
+ latest_reply?: string;
149
+ reply_users?: string[];
150
+ is_locked?: boolean;
151
+ subscribed?: boolean;
152
+ thread_ts?: string;
153
+ parent_user_id?: string;
154
+ }
155
+
156
+ export interface SlackBlock {
157
+ type: string;
158
+ block_id?: string;
159
+ elements?: unknown[];
160
+ text?: {
161
+ type: string;
162
+ text: string;
163
+ emoji?: boolean;
164
+ verbatim?: boolean;
165
+ };
166
+ }
167
+
168
+ export interface SlackAttachment {
169
+ fallback?: string;
170
+ color?: string;
171
+ pretext?: string;
172
+ author_name?: string;
173
+ author_link?: string;
174
+ author_icon?: string;
175
+ title?: string;
176
+ title_link?: string;
177
+ text?: string;
178
+ fields?: {
179
+ title: string;
180
+ value: string;
181
+ short?: boolean;
182
+ }[];
183
+ image_url?: string;
184
+ thumb_url?: string;
185
+ footer?: string;
186
+ footer_icon?: string;
187
+ ts?: number;
188
+ }
189
+
190
+ // API Response types
191
+ export interface SlackApiResponse<T = unknown> {
192
+ ok: boolean;
193
+ error?: string;
194
+ warning?: string;
195
+ response_metadata?: {
196
+ next_cursor?: string;
197
+ scopes?: string[];
198
+ acceptedScopes?: string[];
199
+ };
200
+ [key: string]: unknown;
201
+ }
202
+
203
+ export interface UsersListResponse extends SlackApiResponse {
204
+ members: SlackUser[];
205
+ cache_ts: number;
206
+ }
207
+
208
+ export interface ConversationsListResponse extends SlackApiResponse {
209
+ channels: SlackChannel[];
210
+ }
211
+
212
+ export interface ConversationsHistoryResponse extends SlackApiResponse {
213
+ messages: SlackMessage[];
214
+ has_more: boolean;
215
+ pin_count?: number;
216
+ }
217
+
218
+ export interface ChatPostMessageResponse extends SlackApiResponse {
219
+ channel: string;
220
+ ts: string;
221
+ message: SlackMessage;
222
+ }
223
+
224
+ export interface AuthTestResponse extends SlackApiResponse {
225
+ url: string;
226
+ team: string;
227
+ user: string;
228
+ team_id: string;
229
+ user_id: string;
230
+ bot_id?: string;
231
+ is_enterprise_install?: boolean;
232
+ }
233
+
234
+ // Request options
235
+ export interface ListOptions {
236
+ limit?: number;
237
+ cursor?: string;
238
+ }
239
+
240
+ export interface ConversationsListOptions extends ListOptions {
241
+ types?: string; // 'public_channel,private_channel,mpim,im'
242
+ exclude_archived?: boolean;
243
+ team_id?: string;
244
+ }
245
+
246
+ export interface ConversationsHistoryOptions extends ListOptions {
247
+ channel: string;
248
+ oldest?: string;
249
+ latest?: string;
250
+ inclusive?: boolean;
251
+ }
252
+
253
+ export interface ChatPostMessageOptions {
254
+ channel: string;
255
+ text?: string;
256
+ blocks?: SlackBlock[];
257
+ attachments?: SlackAttachment[];
258
+ thread_ts?: string;
259
+ reply_broadcast?: boolean;
260
+ unfurl_links?: boolean;
261
+ unfurl_media?: boolean;
262
+ mrkdwn?: boolean;
263
+ }
@@ -0,0 +1,297 @@
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, rmSync } from 'fs';
2
+ import { homedir } from 'os';
3
+ import { join } from 'path';
4
+ import type { CliConfig, OAuth2Tokens } from '../types';
5
+
6
+ const CONNECTOR_NAME = 'connect-slack';
7
+ const DEFAULT_PROFILE = 'default';
8
+ const CURRENT_PROFILE_FILE = 'current_profile';
9
+ const PROFILES_DIR = 'profiles';
10
+
11
+ // Store for --profile flag override
12
+ let profileOverride: string | undefined;
13
+
14
+ // Config directory: ~/.connect/connect-slack/
15
+ const BASE_CONFIG_DIR = join(homedir(), '.connect', CONNECTOR_NAME);
16
+
17
+ // ============================================
18
+ // Profile Management
19
+ // ============================================
20
+
21
+ export function setProfileOverride(profile: string | undefined): void {
22
+ profileOverride = profile;
23
+ }
24
+
25
+ export function getProfileOverride(): string | undefined {
26
+ return profileOverride;
27
+ }
28
+
29
+ function ensureBaseConfigDir(): void {
30
+ if (!existsSync(BASE_CONFIG_DIR)) {
31
+ mkdirSync(BASE_CONFIG_DIR, { recursive: true });
32
+ }
33
+ }
34
+
35
+ function getProfilesDir(): string {
36
+ return join(BASE_CONFIG_DIR, PROFILES_DIR);
37
+ }
38
+
39
+ function getCurrentProfileFile(): string {
40
+ return join(BASE_CONFIG_DIR, CURRENT_PROFILE_FILE);
41
+ }
42
+
43
+ /**
44
+ * Get the current active profile name
45
+ */
46
+ export function getCurrentProfile(): string {
47
+ if (profileOverride) {
48
+ return profileOverride;
49
+ }
50
+
51
+ ensureBaseConfigDir();
52
+
53
+ const profilesDir = getProfilesDir();
54
+ if (!existsSync(profilesDir)) {
55
+ mkdirSync(profilesDir, { recursive: true });
56
+ }
57
+
58
+ const currentProfileFile = getCurrentProfileFile();
59
+ if (existsSync(currentProfileFile)) {
60
+ try {
61
+ const profile = readFileSync(currentProfileFile, 'utf-8').trim();
62
+ if (profile && profileExists(profile)) {
63
+ return profile;
64
+ }
65
+ } catch {
66
+ // Fall through to default
67
+ }
68
+ }
69
+
70
+ return DEFAULT_PROFILE;
71
+ }
72
+
73
+ /**
74
+ * Set the current active profile
75
+ */
76
+ export function setCurrentProfile(profile: string): void {
77
+ ensureBaseConfigDir();
78
+
79
+ if (!profileExists(profile) && profile !== DEFAULT_PROFILE) {
80
+ throw new Error(`Profile "${profile}" does not exist. Create it first with "profile create ${profile}"`);
81
+ }
82
+
83
+ writeFileSync(getCurrentProfileFile(), profile);
84
+ }
85
+
86
+ /**
87
+ * Check if a profile exists
88
+ */
89
+ export function profileExists(profile: string): boolean {
90
+ const profileDir = join(getProfilesDir(), profile);
91
+ return existsSync(profileDir);
92
+ }
93
+
94
+ /**
95
+ * List all available profiles
96
+ */
97
+ export function listProfiles(): string[] {
98
+ ensureBaseConfigDir();
99
+
100
+ const profilesDir = getProfilesDir();
101
+ if (!existsSync(profilesDir)) {
102
+ return [];
103
+ }
104
+
105
+ return readdirSync(profilesDir, { withFileTypes: true })
106
+ .filter(dirent => dirent.isDirectory())
107
+ .map(dirent => dirent.name)
108
+ .sort();
109
+ }
110
+
111
+ /**
112
+ * Create a new profile
113
+ */
114
+ export function createProfile(profile: string): void {
115
+ ensureBaseConfigDir();
116
+
117
+ if (profileExists(profile)) {
118
+ throw new Error(`Profile "${profile}" already exists`);
119
+ }
120
+
121
+ // Validate profile name
122
+ if (!/^[a-zA-Z0-9_-]+$/.test(profile)) {
123
+ throw new Error('Profile name can only contain letters, numbers, hyphens, and underscores');
124
+ }
125
+
126
+ const profileDir = join(getProfilesDir(), profile);
127
+ mkdirSync(profileDir, { recursive: true });
128
+ }
129
+
130
+ /**
131
+ * Delete a profile
132
+ */
133
+ export function deleteProfile(profile: string): void {
134
+ if (profile === DEFAULT_PROFILE) {
135
+ throw new Error('Cannot delete the default profile');
136
+ }
137
+
138
+ if (!profileExists(profile)) {
139
+ throw new Error(`Profile "${profile}" does not exist`);
140
+ }
141
+
142
+ const currentProfile = getCurrentProfile();
143
+ if (currentProfile === profile) {
144
+ setCurrentProfile(DEFAULT_PROFILE);
145
+ }
146
+
147
+ const profileDir = join(getProfilesDir(), profile);
148
+ rmSync(profileDir, { recursive: true });
149
+ }
150
+
151
+ /**
152
+ * Get the config directory for the current profile
153
+ */
154
+ function getProfileDir(): string {
155
+ ensureBaseConfigDir();
156
+
157
+ const profilesDir = getProfilesDir();
158
+ if (!existsSync(profilesDir)) {
159
+ mkdirSync(profilesDir, { recursive: true });
160
+ }
161
+
162
+ const profile = getCurrentProfile();
163
+ const profileDir = join(profilesDir, profile);
164
+
165
+ if (!existsSync(profileDir)) {
166
+ mkdirSync(profileDir, { recursive: true });
167
+ }
168
+
169
+ return profileDir;
170
+ }
171
+
172
+ export function getConfigDir(): string {
173
+ return getProfileDir();
174
+ }
175
+
176
+ export function getBaseConfigDir(): string {
177
+ return BASE_CONFIG_DIR;
178
+ }
179
+
180
+ export function ensureConfigDir(): void {
181
+ getProfileDir();
182
+ }
183
+
184
+ // ============================================
185
+ // Config Management
186
+ // ============================================
187
+
188
+ export function loadConfig(): CliConfig {
189
+ ensureConfigDir();
190
+ const configFile = join(getProfileDir(), 'config.json');
191
+
192
+ if (!existsSync(configFile)) {
193
+ return {};
194
+ }
195
+
196
+ try {
197
+ const content = readFileSync(configFile, 'utf-8');
198
+ return JSON.parse(content);
199
+ } catch {
200
+ return {};
201
+ }
202
+ }
203
+
204
+ export function saveConfig(config: CliConfig): void {
205
+ ensureConfigDir();
206
+ const configFile = join(getProfileDir(), 'config.json');
207
+ writeFileSync(configFile, JSON.stringify(config, null, 2));
208
+ }
209
+
210
+ // ============================================
211
+ // Token Management
212
+ // ============================================
213
+
214
+ export function loadTokens(): OAuth2Tokens | null {
215
+ ensureConfigDir();
216
+ const tokensFile = join(getProfileDir(), 'tokens.json');
217
+
218
+ if (!existsSync(tokensFile)) {
219
+ return null;
220
+ }
221
+
222
+ try {
223
+ const content = readFileSync(tokensFile, 'utf-8');
224
+ return JSON.parse(content);
225
+ } catch {
226
+ return null;
227
+ }
228
+ }
229
+
230
+ export function saveTokens(tokens: OAuth2Tokens): void {
231
+ ensureConfigDir();
232
+ const tokensFile = join(getProfileDir(), 'tokens.json');
233
+ writeFileSync(tokensFile, JSON.stringify(tokens, null, 2), { mode: 0o600 });
234
+ }
235
+
236
+ export function getBotToken(): string | undefined {
237
+ return process.env.SLACK_BOT_TOKEN || loadTokens()?.accessToken || loadConfig().botToken;
238
+ }
239
+
240
+ export function getUserToken(): string | undefined {
241
+ return process.env.SLACK_USER_TOKEN || loadTokens()?.authedUser?.accessToken || loadConfig().userToken;
242
+ }
243
+
244
+ export function setBotToken(token: string): void {
245
+ const config = loadConfig();
246
+ config.botToken = token;
247
+ saveConfig(config);
248
+ }
249
+
250
+ export function setUserToken(token: string): void {
251
+ const config = loadConfig();
252
+ config.userToken = token;
253
+ saveConfig(config);
254
+ }
255
+
256
+ export function getTeamId(): string | undefined {
257
+ return process.env.SLACK_TEAM_ID || loadTokens()?.team?.id || loadConfig().teamId;
258
+ }
259
+
260
+ export function getTeamName(): string | undefined {
261
+ return loadTokens()?.team?.name || loadConfig().teamName;
262
+ }
263
+
264
+ export function getDefaultChannel(): string | undefined {
265
+ return loadConfig().defaultChannel;
266
+ }
267
+
268
+ export function setDefaultChannel(channel: string): void {
269
+ const config = loadConfig();
270
+ config.defaultChannel = channel;
271
+ saveConfig(config);
272
+ }
273
+
274
+ // ============================================
275
+ // Utility Functions
276
+ // ============================================
277
+
278
+ export function clearTokens(): void {
279
+ const tokensFile = join(getProfileDir(), 'tokens.json');
280
+ if (existsSync(tokensFile)) {
281
+ rmSync(tokensFile);
282
+ }
283
+ }
284
+
285
+ export function clearConfig(): void {
286
+ saveConfig({});
287
+ clearTokens();
288
+ }
289
+
290
+ export function isAuthenticated(): boolean {
291
+ const token = getBotToken() || getUserToken();
292
+ return token !== undefined && token !== '';
293
+ }
294
+
295
+ export function getActiveProfileName(): string {
296
+ return getCurrentProfile();
297
+ }
@@ -0,0 +1,119 @@
1
+ import chalk from 'chalk';
2
+
3
+ export type OutputFormat = 'json' | 'table' | 'pretty';
4
+
5
+ export function formatOutput(data: unknown, format: OutputFormat = 'pretty'): string {
6
+ switch (format) {
7
+ case 'json':
8
+ return JSON.stringify(data, null, 2);
9
+ case 'table':
10
+ return formatAsTable(data);
11
+ case 'pretty':
12
+ default:
13
+ return formatPretty(data);
14
+ }
15
+ }
16
+
17
+ function formatAsTable(data: unknown): string {
18
+ if (!Array.isArray(data)) {
19
+ data = [data];
20
+ }
21
+
22
+ const items = data as Record<string, unknown>[];
23
+ if (items.length === 0) {
24
+ return 'No data';
25
+ }
26
+
27
+ const firstItem = items[0];
28
+ if (!firstItem || typeof firstItem !== 'object') {
29
+ return 'No data';
30
+ }
31
+
32
+ const keys = Object.keys(firstItem);
33
+ const colWidths = keys.map(key => {
34
+ const maxValue = Math.max(
35
+ key.length,
36
+ ...items.map(item => String(item[key] ?? '').length)
37
+ );
38
+ return Math.min(maxValue, 40);
39
+ });
40
+
41
+ const header = keys.map((key, i) => key.padEnd(colWidths[i] ?? 10)).join(' | ');
42
+ const separator = colWidths.map(w => '-'.repeat(w)).join('-+-');
43
+
44
+ const rows = items.map(item =>
45
+ keys.map((key, i) => {
46
+ const value = String(item[key] ?? '');
47
+ const width = colWidths[i] ?? 10;
48
+ return value.length > width
49
+ ? value.substring(0, width - 3) + '...'
50
+ : value.padEnd(width);
51
+ }).join(' | ')
52
+ );
53
+
54
+ return [header, separator, ...rows].join('\n');
55
+ }
56
+
57
+ function formatPretty(data: unknown): string {
58
+ if (Array.isArray(data)) {
59
+ return data.map((item, i) => `${chalk.cyan(`[${i + 1}]`)} ${formatPrettyItem(item)}`).join('\n\n');
60
+ }
61
+ return formatPrettyItem(data);
62
+ }
63
+
64
+ function formatPrettyItem(item: unknown, indent = 0): string {
65
+ if (item === null || item === undefined) {
66
+ return chalk.gray('null');
67
+ }
68
+
69
+ if (typeof item !== 'object') {
70
+ return String(item);
71
+ }
72
+
73
+ const spaces = ' '.repeat(indent);
74
+ const entries = Object.entries(item as Record<string, unknown>);
75
+
76
+ return entries
77
+ .map(([key, value]) => {
78
+ if (Array.isArray(value)) {
79
+ if (value.length === 0) {
80
+ return `${spaces}${chalk.blue(key)}: ${chalk.gray('[]')}`;
81
+ }
82
+ if (typeof value[0] === 'object') {
83
+ return `${spaces}${chalk.blue(key)}:\n${value.map(v => formatPrettyItem(v, indent + 1)).join('\n')}`;
84
+ }
85
+ return `${spaces}${chalk.blue(key)}: ${value.join(', ')}`;
86
+ }
87
+
88
+ if (typeof value === 'object' && value !== null) {
89
+ return `${spaces}${chalk.blue(key)}:\n${formatPrettyItem(value, indent + 1)}`;
90
+ }
91
+
92
+ return `${spaces}${chalk.blue(key)}: ${chalk.white(String(value))}`;
93
+ })
94
+ .join('\n');
95
+ }
96
+
97
+ export function success(message: string): void {
98
+ console.log(chalk.green('✓'), message);
99
+ }
100
+
101
+ export function error(message: string): void {
102
+ console.error(chalk.red('✗'), message);
103
+ }
104
+
105
+ export function warn(message: string): void {
106
+ console.warn(chalk.yellow('⚠'), message);
107
+ }
108
+
109
+ export function info(message: string): void {
110
+ console.log(chalk.blue('ℹ'), message);
111
+ }
112
+
113
+ export function heading(message: string): void {
114
+ console.log(chalk.bold.cyan(`\n${message}\n`));
115
+ }
116
+
117
+ export function print(data: unknown, format: OutputFormat = 'pretty'): void {
118
+ console.log(formatOutput(data, format));
119
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "esModuleInterop": true,
7
+ "strict": true,
8
+ "skipLibCheck": true,
9
+ "declaration": true,
10
+ "outDir": "./dist",
11
+ "rootDir": "./src",
12
+ "types": ["bun-types"]
13
+ },
14
+ "include": ["src/**/*"],
15
+ "exclude": ["node_modules", "dist", "bin"]
16
+ }
@@ -0,0 +1,2 @@
1
+ # Telegram Bot Token (get from @BotFather)
2
+ TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrSTUvwxYZ