@plosson/agentio 0.1.11 ā 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/auth/token-store.ts +8 -0
- package/src/commands/config.ts +238 -0
- package/src/commands/gchat.ts +2 -17
- package/src/commands/gmail.ts +2 -2
- package/src/commands/jira.ts +2 -17
- package/src/commands/slack.ts +2 -17
- package/src/commands/telegram.ts +2 -17
- package/src/index.ts +2 -0
- package/src/utils/stdin.ts +63 -0
package/package.json
CHANGED
package/src/auth/token-store.ts
CHANGED
|
@@ -112,3 +112,11 @@ export async function hasCredentials(
|
|
|
112
112
|
const credentials = await loadCredentials();
|
|
113
113
|
return !!credentials[service]?.[profile];
|
|
114
114
|
}
|
|
115
|
+
|
|
116
|
+
export async function getAllCredentials(): Promise<StoredCredentials> {
|
|
117
|
+
return loadCredentials();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export async function setAllCredentials(credentials: StoredCredentials): Promise<void> {
|
|
121
|
+
return saveCredentials(credentials);
|
|
122
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'crypto';
|
|
3
|
+
import { readFile, writeFile } from 'fs/promises';
|
|
4
|
+
import { existsSync } from 'fs';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import { loadConfig, saveConfig } from '../config/config-manager';
|
|
7
|
+
import { getAllCredentials, setAllCredentials } from '../auth/token-store';
|
|
8
|
+
import { CliError, handleError } from '../utils/errors';
|
|
9
|
+
import type { Config } from '../types/config';
|
|
10
|
+
import type { StoredCredentials } from '../types/tokens';
|
|
11
|
+
|
|
12
|
+
const ALGORITHM = 'aes-256-gcm';
|
|
13
|
+
const DEFAULT_EXPORT_FILE = 'agentio.config';
|
|
14
|
+
|
|
15
|
+
interface ExportedData {
|
|
16
|
+
version: number;
|
|
17
|
+
config: Config;
|
|
18
|
+
credentials: StoredCredentials;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function deriveKeyFromPassword(password: string): Buffer {
|
|
22
|
+
return scryptSync(password, 'agentio-export-salt', 32);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function generateKey(): string {
|
|
26
|
+
return randomBytes(32).toString('hex');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function encrypt(data: string, key: Buffer): { iv: string; tag: string; data: string } {
|
|
30
|
+
const iv = randomBytes(16);
|
|
31
|
+
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
32
|
+
|
|
33
|
+
const encrypted = Buffer.concat([
|
|
34
|
+
cipher.update(data, 'utf-8'),
|
|
35
|
+
cipher.final(),
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
const tag = cipher.getAuthTag();
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
iv: iv.toString('hex'),
|
|
42
|
+
tag: tag.toString('hex'),
|
|
43
|
+
data: encrypted.toString('hex'),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function decrypt(encrypted: { iv: string; tag: string; data: string }, key: Buffer): string {
|
|
48
|
+
const decipher = createDecipheriv(ALGORITHM, key, Buffer.from(encrypted.iv, 'hex'));
|
|
49
|
+
decipher.setAuthTag(Buffer.from(encrypted.tag, 'hex'));
|
|
50
|
+
|
|
51
|
+
const decrypted = Buffer.concat([
|
|
52
|
+
decipher.update(Buffer.from(encrypted.data, 'hex')),
|
|
53
|
+
decipher.final(),
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
return decrypted.toString('utf-8');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function registerConfigCommands(program: Command): void {
|
|
60
|
+
const config = program
|
|
61
|
+
.command('config')
|
|
62
|
+
.description('Configuration management');
|
|
63
|
+
|
|
64
|
+
config
|
|
65
|
+
.command('export')
|
|
66
|
+
.description('Export configuration and credentials to an encrypted file')
|
|
67
|
+
.option('--key <key>', 'Encryption key (64 hex characters). If not provided, a random key will be generated')
|
|
68
|
+
.option('--output <file>', 'Output file path', DEFAULT_EXPORT_FILE)
|
|
69
|
+
.action(async (options) => {
|
|
70
|
+
try {
|
|
71
|
+
// Validate key if provided
|
|
72
|
+
let encryptionKey: string;
|
|
73
|
+
if (options.key) {
|
|
74
|
+
if (!/^[0-9a-fA-F]{64}$/.test(options.key)) {
|
|
75
|
+
throw new CliError(
|
|
76
|
+
'INVALID_PARAMS',
|
|
77
|
+
'Invalid encryption key format',
|
|
78
|
+
'Key must be exactly 64 hexadecimal characters'
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
encryptionKey = options.key;
|
|
82
|
+
} else {
|
|
83
|
+
encryptionKey = generateKey();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Load config and credentials
|
|
87
|
+
const configData = await loadConfig();
|
|
88
|
+
const credentials = await getAllCredentials();
|
|
89
|
+
|
|
90
|
+
const exportData: ExportedData = {
|
|
91
|
+
version: 1,
|
|
92
|
+
config: configData,
|
|
93
|
+
credentials,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Encrypt the data
|
|
97
|
+
const key = deriveKeyFromPassword(encryptionKey);
|
|
98
|
+
const encrypted = encrypt(JSON.stringify(exportData), key);
|
|
99
|
+
|
|
100
|
+
// Write to file
|
|
101
|
+
const outputPath = join(process.cwd(), options.output);
|
|
102
|
+
await writeFile(outputPath, JSON.stringify(encrypted, null, 2), { mode: 0o600 });
|
|
103
|
+
|
|
104
|
+
console.log(`Configuration exported to: ${outputPath}`);
|
|
105
|
+
console.log(`Encryption key: ${encryptionKey}`);
|
|
106
|
+
console.log('');
|
|
107
|
+
console.log('Keep this key safe! You will need it to import the configuration.');
|
|
108
|
+
} catch (error) {
|
|
109
|
+
handleError(error);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
config
|
|
114
|
+
.command('import')
|
|
115
|
+
.description('Import configuration and credentials from an encrypted file')
|
|
116
|
+
.argument('<file>', 'Path to the encrypted configuration file')
|
|
117
|
+
.option('--key <key>', 'Encryption key (64 hex characters). Falls back to AGENTIO_KEY env var')
|
|
118
|
+
.option('--merge', 'Merge with existing configuration instead of replacing')
|
|
119
|
+
.action(async (file, options) => {
|
|
120
|
+
try {
|
|
121
|
+
// Get key from option or environment variable
|
|
122
|
+
const key = options.key || process.env.AGENTIO_KEY;
|
|
123
|
+
if (!key) {
|
|
124
|
+
throw new CliError(
|
|
125
|
+
'INVALID_PARAMS',
|
|
126
|
+
'No encryption key provided',
|
|
127
|
+
'Provide --key option or set AGENTIO_KEY environment variable'
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Validate key
|
|
132
|
+
if (!/^[0-9a-fA-F]{64}$/.test(key)) {
|
|
133
|
+
throw new CliError(
|
|
134
|
+
'INVALID_PARAMS',
|
|
135
|
+
'Invalid encryption key format',
|
|
136
|
+
'Key must be exactly 64 hexadecimal characters'
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Check file exists
|
|
141
|
+
const filePath = file.startsWith('/') ? file : join(process.cwd(), file);
|
|
142
|
+
if (!existsSync(filePath)) {
|
|
143
|
+
throw new CliError(
|
|
144
|
+
'NOT_FOUND',
|
|
145
|
+
`File not found: ${filePath}`,
|
|
146
|
+
'Provide a valid path to the exported configuration file'
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Read and parse the encrypted file
|
|
151
|
+
const encryptedContent = await readFile(filePath, 'utf-8');
|
|
152
|
+
let encrypted: { iv: string; tag: string; data: string };
|
|
153
|
+
try {
|
|
154
|
+
encrypted = JSON.parse(encryptedContent);
|
|
155
|
+
} catch {
|
|
156
|
+
throw new CliError(
|
|
157
|
+
'INVALID_PARAMS',
|
|
158
|
+
'Invalid file format',
|
|
159
|
+
'The file does not appear to be a valid agentio export file'
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Decrypt
|
|
164
|
+
const derivedKey = deriveKeyFromPassword(key);
|
|
165
|
+
let exportData: ExportedData;
|
|
166
|
+
try {
|
|
167
|
+
const decrypted = decrypt(encrypted, derivedKey);
|
|
168
|
+
exportData = JSON.parse(decrypted);
|
|
169
|
+
} catch {
|
|
170
|
+
throw new CliError(
|
|
171
|
+
'AUTH_FAILED',
|
|
172
|
+
'Failed to decrypt configuration',
|
|
173
|
+
'Check that you are using the correct encryption key'
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Validate version
|
|
178
|
+
if (exportData.version !== 1) {
|
|
179
|
+
throw new CliError(
|
|
180
|
+
'INVALID_PARAMS',
|
|
181
|
+
`Unsupported export version: ${exportData.version}`,
|
|
182
|
+
'This version of agentio may not support this export format'
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (options.merge) {
|
|
187
|
+
// Merge with existing config
|
|
188
|
+
const currentConfig = await loadConfig();
|
|
189
|
+
const currentCredentials = await getAllCredentials();
|
|
190
|
+
|
|
191
|
+
// Merge profiles
|
|
192
|
+
for (const [service, profiles] of Object.entries(exportData.config.profiles)) {
|
|
193
|
+
if (profiles) {
|
|
194
|
+
if (!currentConfig.profiles[service as keyof typeof currentConfig.profiles]) {
|
|
195
|
+
(currentConfig.profiles as Record<string, string[]>)[service] = [];
|
|
196
|
+
}
|
|
197
|
+
for (const profile of profiles) {
|
|
198
|
+
if (!(currentConfig.profiles as Record<string, string[]>)[service].includes(profile)) {
|
|
199
|
+
(currentConfig.profiles as Record<string, string[]>)[service].push(profile);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Merge defaults (only if not set)
|
|
206
|
+
for (const [service, defaultProfile] of Object.entries(exportData.config.defaults)) {
|
|
207
|
+
if (defaultProfile && !(currentConfig.defaults as Record<string, string | undefined>)[service]) {
|
|
208
|
+
(currentConfig.defaults as Record<string, string>)[service] = defaultProfile;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Merge credentials
|
|
213
|
+
for (const [service, profiles] of Object.entries(exportData.credentials)) {
|
|
214
|
+
if (!currentCredentials[service]) {
|
|
215
|
+
currentCredentials[service] = {};
|
|
216
|
+
}
|
|
217
|
+
for (const [profile, creds] of Object.entries(profiles)) {
|
|
218
|
+
// Only add if not already exists
|
|
219
|
+
if (!currentCredentials[service][profile]) {
|
|
220
|
+
currentCredentials[service][profile] = creds;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
await saveConfig(currentConfig);
|
|
226
|
+
await setAllCredentials(currentCredentials);
|
|
227
|
+
console.log('Configuration merged successfully');
|
|
228
|
+
} else {
|
|
229
|
+
// Replace existing config
|
|
230
|
+
await saveConfig(exportData.config);
|
|
231
|
+
await setAllCredentials(exportData.credentials);
|
|
232
|
+
console.log('Configuration imported successfully');
|
|
233
|
+
}
|
|
234
|
+
} catch (error) {
|
|
235
|
+
handleError(error);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
}
|
package/src/commands/gchat.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { google } from 'googleapis';
|
|
3
|
-
import { createInterface } from 'readline';
|
|
4
3
|
import { readFile } from 'fs/promises';
|
|
5
4
|
import { setCredentials, removeCredentials, getCredentials } from '../auth/token-store';
|
|
6
5
|
import { setProfile, removeProfile, listProfiles, getProfile } from '../config/config-manager';
|
|
@@ -8,24 +7,10 @@ import { performOAuthFlow } from '../auth/oauth';
|
|
|
8
7
|
import { createGoogleAuth } from '../auth/token-manager';
|
|
9
8
|
import { GChatClient } from '../services/gchat/client';
|
|
10
9
|
import { CliError, handleError } from '../utils/errors';
|
|
11
|
-
import { readStdin } from '../utils/stdin';
|
|
10
|
+
import { readStdin, prompt, resolveProfileName } from '../utils/stdin';
|
|
12
11
|
import { printGChatSendResult, printGChatMessageList, printGChatMessage } from '../utils/output';
|
|
13
12
|
import type { GChatCredentials, GChatWebhookCredentials, GChatOAuthCredentials } from '../types/gchat';
|
|
14
13
|
|
|
15
|
-
function prompt(question: string): Promise<string> {
|
|
16
|
-
const rl = createInterface({
|
|
17
|
-
input: process.stdin,
|
|
18
|
-
output: process.stderr,
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
return new Promise((resolve) => {
|
|
22
|
-
rl.question(question, (answer) => {
|
|
23
|
-
rl.close();
|
|
24
|
-
resolve(answer.trim());
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
|
|
29
14
|
async function getGChatClient(profileName?: string): Promise<{ client: GChatClient; profile: string }> {
|
|
30
15
|
const profile = await getProfile('gchat', profileName);
|
|
31
16
|
|
|
@@ -195,7 +180,7 @@ export function registerGChatCommands(program: Command): void {
|
|
|
195
180
|
.option('--profile <name>', 'Profile name', 'default')
|
|
196
181
|
.action(async (options) => {
|
|
197
182
|
try {
|
|
198
|
-
const profileName = options.profile;
|
|
183
|
+
const profileName = await resolveProfileName('gchat', options.profile);
|
|
199
184
|
|
|
200
185
|
console.error('\nGoogle Chat Setup\n');
|
|
201
186
|
|
package/src/commands/gmail.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { performOAuthFlow } from '../auth/oauth';
|
|
|
8
8
|
import { GmailClient } from '../services/gmail/client';
|
|
9
9
|
import { printMessageList, printMessage, printSendResult, printArchived, printMarked, raw } from '../utils/output';
|
|
10
10
|
import { CliError, handleError } from '../utils/errors';
|
|
11
|
-
import { readStdin } from '../utils/stdin';
|
|
11
|
+
import { readStdin, resolveProfileName } from '../utils/stdin';
|
|
12
12
|
import type { GmailAttachment } from '../types/gmail';
|
|
13
13
|
|
|
14
14
|
async function getGmailClient(profileName?: string): Promise<{ client: GmailClient; profile: string }> {
|
|
@@ -245,7 +245,7 @@ Query Syntax Examples:
|
|
|
245
245
|
.option('--profile <name>', 'Profile name', 'default')
|
|
246
246
|
.action(async (options) => {
|
|
247
247
|
try {
|
|
248
|
-
const profileName = options.profile;
|
|
248
|
+
const profileName = await resolveProfileName('gmail', options.profile);
|
|
249
249
|
|
|
250
250
|
console.error(`Starting OAuth flow for Gmail profile "${profileName}"...`);
|
|
251
251
|
|
package/src/commands/jira.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { createInterface } from 'readline';
|
|
3
2
|
import { setCredentials, removeCredentials, getCredentials, setCredentials as updateCredentials } from '../auth/token-store';
|
|
4
3
|
import { setProfile, removeProfile, listProfiles, getProfile } from '../config/config-manager';
|
|
5
4
|
import { performJiraOAuthFlow, refreshJiraToken, type AtlassianSite } from '../auth/jira-oauth';
|
|
6
5
|
import { JiraClient } from '../services/jira/client';
|
|
7
6
|
import { CliError, handleError } from '../utils/errors';
|
|
8
|
-
import { readStdin } from '../utils/stdin';
|
|
7
|
+
import { readStdin, prompt, resolveProfileName } from '../utils/stdin';
|
|
9
8
|
import {
|
|
10
9
|
printJiraProjectList,
|
|
11
10
|
printJiraIssueList,
|
|
@@ -16,20 +15,6 @@ import {
|
|
|
16
15
|
} from '../utils/output';
|
|
17
16
|
import type { JiraCredentials } from '../types/jira';
|
|
18
17
|
|
|
19
|
-
function prompt(question: string): Promise<string> {
|
|
20
|
-
const rl = createInterface({
|
|
21
|
-
input: process.stdin,
|
|
22
|
-
output: process.stderr,
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
return new Promise((resolve) => {
|
|
26
|
-
rl.question(question, (answer) => {
|
|
27
|
-
rl.close();
|
|
28
|
-
resolve(answer.trim());
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
|
|
33
18
|
async function ensureValidToken(credentials: JiraCredentials, profile: string): Promise<JiraCredentials> {
|
|
34
19
|
// Check if token is expired or about to expire (within 5 minutes)
|
|
35
20
|
const bufferTime = 5 * 60 * 1000;
|
|
@@ -228,7 +213,7 @@ export function registerJiraCommands(program: Command): void {
|
|
|
228
213
|
.option('--profile <name>', 'Profile name', 'default')
|
|
229
214
|
.action(async (options) => {
|
|
230
215
|
try {
|
|
231
|
-
const profileName = options.profile;
|
|
216
|
+
const profileName = await resolveProfileName('jira', options.profile);
|
|
232
217
|
|
|
233
218
|
console.error('\nš§ JIRA OAuth Setup\n');
|
|
234
219
|
|
package/src/commands/slack.ts
CHANGED
|
@@ -1,28 +1,13 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { createInterface } from 'readline';
|
|
3
2
|
import { readFile } from 'fs/promises';
|
|
4
3
|
import { setCredentials, removeCredentials, getCredentials } from '../auth/token-store';
|
|
5
4
|
import { setProfile, removeProfile, listProfiles, getProfile } from '../config/config-manager';
|
|
6
5
|
import { SlackClient } from '../services/slack/client';
|
|
7
6
|
import { CliError, handleError } from '../utils/errors';
|
|
8
|
-
import { readStdin } from '../utils/stdin';
|
|
7
|
+
import { readStdin, prompt, resolveProfileName } from '../utils/stdin';
|
|
9
8
|
import { printSlackSendResult } from '../utils/output';
|
|
10
9
|
import type { SlackCredentials, SlackWebhookCredentials } from '../types/slack';
|
|
11
10
|
|
|
12
|
-
function prompt(question: string): Promise<string> {
|
|
13
|
-
const rl = createInterface({
|
|
14
|
-
input: process.stdin,
|
|
15
|
-
output: process.stderr,
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
return new Promise((resolve) => {
|
|
19
|
-
rl.question(question, (answer) => {
|
|
20
|
-
rl.close();
|
|
21
|
-
resolve(answer.trim());
|
|
22
|
-
});
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
|
|
26
11
|
async function getSlackClient(profileName?: string): Promise<{ client: SlackClient; profile: string }> {
|
|
27
12
|
const profile = await getProfile('slack', profileName);
|
|
28
13
|
|
|
@@ -149,7 +134,7 @@ export function registerSlackCommands(program: Command): void {
|
|
|
149
134
|
.option('--profile <name>', 'Profile name', 'default')
|
|
150
135
|
.action(async (options) => {
|
|
151
136
|
try {
|
|
152
|
-
const profileName = options.profile;
|
|
137
|
+
const profileName = await resolveProfileName('slack', options.profile);
|
|
153
138
|
await setupWebhookProfile(profileName);
|
|
154
139
|
} catch (error) {
|
|
155
140
|
handleError(error);
|
package/src/commands/telegram.ts
CHANGED
|
@@ -1,26 +1,11 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { createInterface } from 'readline';
|
|
3
2
|
import { setCredentials, removeCredentials, getCredentials } from '../auth/token-store';
|
|
4
3
|
import { setProfile, removeProfile, listProfiles, getProfile } from '../config/config-manager';
|
|
5
4
|
import { TelegramClient } from '../services/telegram/client';
|
|
6
5
|
import { CliError, handleError } from '../utils/errors';
|
|
7
|
-
import { readStdin } from '../utils/stdin';
|
|
6
|
+
import { readStdin, prompt, resolveProfileName } from '../utils/stdin';
|
|
8
7
|
import type { TelegramCredentials, TelegramSendOptions } from '../types/telegram';
|
|
9
8
|
|
|
10
|
-
function prompt(question: string): Promise<string> {
|
|
11
|
-
const rl = createInterface({
|
|
12
|
-
input: process.stdin,
|
|
13
|
-
output: process.stderr,
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
return new Promise((resolve) => {
|
|
17
|
-
rl.question(question, (answer) => {
|
|
18
|
-
rl.close();
|
|
19
|
-
resolve(answer.trim());
|
|
20
|
-
});
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
|
|
24
9
|
async function getTelegramClient(profileName?: string): Promise<{ client: TelegramClient; profile: string }> {
|
|
25
10
|
const profile = await getProfile('telegram', profileName);
|
|
26
11
|
|
|
@@ -107,7 +92,7 @@ export function registerTelegramCommands(program: Command): void {
|
|
|
107
92
|
.option('--profile <name>', 'Profile name', 'default')
|
|
108
93
|
.action(async (options) => {
|
|
109
94
|
try {
|
|
110
|
-
const profileName = options.profile;
|
|
95
|
+
const profileName = await resolveProfileName('telegram', options.profile);
|
|
111
96
|
|
|
112
97
|
console.error('\nš± Telegram Bot Setup\n');
|
|
113
98
|
|
package/src/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { registerGChatCommands } from './commands/gchat';
|
|
|
6
6
|
import { registerJiraCommands } from './commands/jira';
|
|
7
7
|
import { registerSlackCommands } from './commands/slack';
|
|
8
8
|
import { registerUpdateCommand } from './commands/update';
|
|
9
|
+
import { registerConfigCommands } from './commands/config';
|
|
9
10
|
|
|
10
11
|
declare const BUILD_VERSION: string | undefined;
|
|
11
12
|
|
|
@@ -30,5 +31,6 @@ registerGChatCommands(program);
|
|
|
30
31
|
registerJiraCommands(program);
|
|
31
32
|
registerSlackCommands(program);
|
|
32
33
|
registerUpdateCommand(program);
|
|
34
|
+
registerConfigCommands(program);
|
|
33
35
|
|
|
34
36
|
program.parse();
|
package/src/utils/stdin.ts
CHANGED
|
@@ -1,3 +1,66 @@
|
|
|
1
|
+
import { createInterface } from 'readline';
|
|
2
|
+
import { getProfile } from '../config/config-manager';
|
|
3
|
+
import type { ServiceName } from '../types/config';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Prompt the user for input with a question.
|
|
7
|
+
*/
|
|
8
|
+
export function prompt(question: string): Promise<string> {
|
|
9
|
+
const rl = createInterface({
|
|
10
|
+
input: process.stdin,
|
|
11
|
+
output: process.stderr,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
rl.question(question, (answer) => {
|
|
16
|
+
rl.close();
|
|
17
|
+
resolve(answer.trim());
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Prompt for yes/no confirmation.
|
|
24
|
+
*/
|
|
25
|
+
export async function confirm(question: string): Promise<boolean> {
|
|
26
|
+
const answer = await prompt(`${question} (y/n): `);
|
|
27
|
+
return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Resolve profile name, checking for existing profiles and prompting for override.
|
|
32
|
+
* If the profile already exists, asks the user whether to override or choose a new name.
|
|
33
|
+
*/
|
|
34
|
+
export async function resolveProfileName(
|
|
35
|
+
service: ServiceName,
|
|
36
|
+
requestedName: string
|
|
37
|
+
): Promise<string> {
|
|
38
|
+
const existingProfile = await getProfile(service, requestedName);
|
|
39
|
+
|
|
40
|
+
if (!existingProfile) {
|
|
41
|
+
// Profile doesn't exist, use the requested name
|
|
42
|
+
return requestedName;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Profile exists, ask if user wants to override
|
|
46
|
+
console.error(`\nProfile "${requestedName}" already exists for ${service}.`);
|
|
47
|
+
const shouldOverride = await confirm('Do you want to override it?');
|
|
48
|
+
|
|
49
|
+
if (shouldOverride) {
|
|
50
|
+
return requestedName;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Ask for a new profile name
|
|
54
|
+
const newName = await prompt('Enter a new profile name: ');
|
|
55
|
+
|
|
56
|
+
if (!newName) {
|
|
57
|
+
throw new Error('Profile name is required');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Recursively check if the new name also exists
|
|
61
|
+
return resolveProfileName(service, newName);
|
|
62
|
+
}
|
|
63
|
+
|
|
1
64
|
export async function readStdin(): Promise<string | null> {
|
|
2
65
|
// Check if stdin is a TTY (interactive terminal)
|
|
3
66
|
if (process.stdin.isTTY) {
|