@plosson/agentio 0.2.3 → 0.3.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plosson/agentio",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "description": "CLI for LLM agents to interact with communication and tracking services",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/auth/oauth.ts CHANGED
@@ -14,6 +14,8 @@ const GCHAT_SCOPES = [
14
14
  'https://www.googleapis.com/auth/chat.messages.create', // send messages
15
15
  'https://www.googleapis.com/auth/chat.messages.readonly', // read messages (get operations)
16
16
  'https://www.googleapis.com/auth/chat.spaces.readonly', // read space info and list
17
+ 'https://www.googleapis.com/auth/chat.memberships.readonly', // read space members
18
+ 'https://www.googleapis.com/auth/userinfo.email', // get user email for profile naming
17
19
  ];
18
20
 
19
21
  const PORT_RANGE_START = 3000;
@@ -10,16 +10,14 @@ const TOKEN_EXPIRY_BUFFER_MS = 5 * 60 * 1000; // 5 minutes
10
10
 
11
11
  export async function getValidTokens(
12
12
  service: ServiceName,
13
- profileName?: string
13
+ profileName: string
14
14
  ): Promise<{ tokens: OAuthTokens; profile: string }> {
15
15
  const profile = await getProfile(service, profileName);
16
16
 
17
17
  if (!profile) {
18
18
  throw new CliError(
19
19
  'PROFILE_NOT_FOUND',
20
- profileName
21
- ? `Profile "${profileName}" not found for ${service}`
22
- : `No default profile configured for ${service}`,
20
+ `Profile "${profileName}" not found for ${service}`,
23
21
  `Run: agentio ${service} profile add`
24
22
  );
25
23
  }
@@ -231,13 +231,6 @@ export function registerConfigCommands(program: Command): void {
231
231
  }
232
232
  }
233
233
 
234
- // Merge defaults (only if not set)
235
- for (const [service, defaultProfile] of Object.entries(exportData.config.defaults)) {
236
- if (defaultProfile && !(currentConfig.defaults as Record<string, string | undefined>)[service]) {
237
- (currentConfig.defaults as Record<string, string>)[service] = defaultProfile;
238
- }
239
- }
240
-
241
234
  // Merge credentials
242
235
  for (const [service, profiles] of Object.entries(exportData.credentials)) {
243
236
  if (!currentCredentials[service]) {
@@ -4,7 +4,7 @@ import { setProfile, getProfile } from '../config/config-manager';
4
4
  import { createProfileCommands } from '../utils/profile-commands';
5
5
  import { DiscourseClient } from '../services/discourse/client';
6
6
  import { CliError, handleError } from '../utils/errors';
7
- import { prompt, resolveProfileName } from '../utils/stdin';
7
+ import { prompt } from '../utils/stdin';
8
8
  import type { DiscourseCredentials } from '../types/discourse';
9
9
  import {
10
10
  printDiscourseTopicList,
@@ -13,16 +13,14 @@ import {
13
13
  } from '../utils/output';
14
14
 
15
15
  async function getDiscourseClient(
16
- profileName?: string
16
+ profileName: string
17
17
  ): Promise<{ client: DiscourseClient; profile: string }> {
18
18
  const profile = await getProfile('discourse', profileName);
19
19
 
20
20
  if (!profile) {
21
21
  throw new CliError(
22
22
  'PROFILE_NOT_FOUND',
23
- profileName
24
- ? `Profile "${profileName}" not found for discourse`
25
- : 'No default profile configured for discourse',
23
+ `Profile "${profileName}" not found for discourse`,
26
24
  'Run: agentio discourse profile add'
27
25
  );
28
26
  }
@@ -50,7 +48,7 @@ export function registerDiscourseCommands(program: Command): void {
50
48
  discourse
51
49
  .command('list')
52
50
  .description('List latest topics')
53
- .option('--profile <name>', 'Profile name')
51
+ .requiredOption('--profile <name>', 'Profile name')
54
52
  .option('--category <slug>', 'Filter by category slug or name')
55
53
  .option('--page <number>', 'Page number (0-indexed)', '0')
56
54
  .action(async (options) => {
@@ -71,7 +69,7 @@ export function registerDiscourseCommands(program: Command): void {
71
69
  .command('get')
72
70
  .description('Get a topic with its posts')
73
71
  .argument('<topic-id>', 'Topic ID')
74
- .option('--profile <name>', 'Profile name')
72
+ .requiredOption('--profile <name>', 'Profile name')
75
73
  .action(async (topicId: string, options) => {
76
74
  try {
77
75
  const id = parseInt(topicId, 10);
@@ -91,7 +89,7 @@ export function registerDiscourseCommands(program: Command): void {
91
89
  discourse
92
90
  .command('categories')
93
91
  .description('List all categories')
94
- .option('--profile <name>', 'Profile name')
92
+ .requiredOption('--profile <name>', 'Profile name')
95
93
  .action(async (options) => {
96
94
  try {
97
95
  const { client } = await getDiscourseClient(options.profile);
@@ -112,11 +110,9 @@ export function registerDiscourseCommands(program: Command): void {
112
110
  profile
113
111
  .command('add')
114
112
  .description('Add a new Discourse profile')
115
- .option('--profile <name>', 'Profile name', 'default')
113
+ .option('--profile <name>', 'Profile name (auto-detected from username if not provided)')
116
114
  .action(async (options) => {
117
115
  try {
118
- const profileName = await resolveProfileName('discourse', options.profile);
119
-
120
116
  console.error('\nDiscourse Setup\n');
121
117
 
122
118
  // Step 1: Get base URL
@@ -196,6 +192,9 @@ export function registerDiscourseCommands(program: Command): void {
196
192
  console.error(`\nConnected to ${normalizedUrl}`);
197
193
  console.error(`Authenticated as ${username}\n`);
198
194
 
195
+ // Auto-name based on username
196
+ const profileName = options.profile || username.trim();
197
+
199
198
  // Save credentials
200
199
  await setProfile('discourse', profileName);
201
200
  await setCredentials('discourse', profileName, credentials);
@@ -8,19 +8,17 @@ import { performOAuthFlow } from '../auth/oauth';
8
8
  import { createGoogleAuth } from '../auth/token-manager';
9
9
  import { GChatClient } from '../services/gchat/client';
10
10
  import { CliError, handleError } from '../utils/errors';
11
- import { readStdin, prompt, resolveProfileName } from '../utils/stdin';
12
- import { printGChatSendResult, printGChatMessageList, printGChatMessage } from '../utils/output';
11
+ import { readStdin, prompt } from '../utils/stdin';
12
+ import { printGChatSendResult, printGChatMessageList, printGChatMessage, printGChatSpaceList } from '../utils/output';
13
13
  import type { GChatCredentials, GChatWebhookCredentials, GChatOAuthCredentials } from '../types/gchat';
14
14
 
15
- async function getGChatClient(profileName?: string): Promise<{ client: GChatClient; profile: string }> {
15
+ async function getGChatClient(profileName: string): Promise<{ client: GChatClient; profile: string }> {
16
16
  const profile = await getProfile('gchat', profileName);
17
17
 
18
18
  if (!profile) {
19
19
  throw new CliError(
20
20
  'PROFILE_NOT_FOUND',
21
- profileName
22
- ? `Profile "${profileName}" not found for gchat`
23
- : 'No default profile configured for gchat',
21
+ `Profile "${profileName}" not found for gchat`,
24
22
  'Run: agentio gchat profile add'
25
23
  );
26
24
  }
@@ -49,7 +47,7 @@ export function registerGChatCommands(program: Command): void {
49
47
  gchat
50
48
  .command('send')
51
49
  .description('Send a message to Google Chat')
52
- .option('--profile <name>', 'Profile name')
50
+ .requiredOption('--profile <name>', 'Profile name')
53
51
  .option('--space <id>', 'Space ID (required for OAuth profiles)')
54
52
  .option('--thread <id>', 'Thread ID (optional)')
55
53
  .option('--json [file]', 'Send rich message from JSON file (or stdin if no file specified)')
@@ -134,15 +132,19 @@ export function registerGChatCommands(program: Command): void {
134
132
  gchat
135
133
  .command('list')
136
134
  .description('List messages from a Google Chat space (OAuth profiles only)')
137
- .option('--profile <name>', 'Profile name')
135
+ .requiredOption('--profile <name>', 'Profile name')
138
136
  .requiredOption('--space <id>', 'Space ID')
139
137
  .option('--limit <n>', 'Number of messages', '10')
138
+ .option('--thread <id>', 'Filter by thread ID')
139
+ .option('--since <date>', 'Only messages after this date (YYYY-MM-DD)')
140
140
  .action(async (options) => {
141
141
  try {
142
142
  const { client } = await getGChatClient(options.profile);
143
143
  const messages = await client.list({
144
144
  spaceId: options.space,
145
145
  limit: parseInt(options.limit, 10),
146
+ threadId: options.thread,
147
+ since: options.since ? new Date(options.since) : undefined,
146
148
  });
147
149
 
148
150
  printGChatMessageList(messages);
@@ -154,7 +156,7 @@ export function registerGChatCommands(program: Command): void {
154
156
  gchat
155
157
  .command('get <message-id>')
156
158
  .description('Get a message from a Google Chat space (OAuth profiles only)')
157
- .option('--profile <name>', 'Profile name')
159
+ .requiredOption('--profile <name>', 'Profile name')
158
160
  .requiredOption('--space <id>', 'Space ID')
159
161
  .action(async (messageId: string, options) => {
160
162
  try {
@@ -170,6 +172,27 @@ export function registerGChatCommands(program: Command): void {
170
172
  }
171
173
  });
172
174
 
175
+ gchat
176
+ .command('spaces')
177
+ .description('List available Google Chat spaces (OAuth profiles only)')
178
+ .requiredOption('--profile <name>', 'Profile name')
179
+ .option('--filter <text>', 'Filter spaces by name (case-insensitive)')
180
+ .action(async (options) => {
181
+ try {
182
+ const { client } = await getGChatClient(options.profile);
183
+ let spaces = await client.listSpaces();
184
+
185
+ if (options.filter) {
186
+ const filterLower = options.filter.toLowerCase();
187
+ spaces = spaces.filter(s => s.displayName.toLowerCase().includes(filterLower));
188
+ }
189
+
190
+ printGChatSpaceList(spaces);
191
+ } catch (error) {
192
+ handleError(error);
193
+ }
194
+ });
195
+
173
196
  // Profile management
174
197
  const profile = createProfileCommands<GChatCredentials>(gchat, {
175
198
  service: 'gchat',
@@ -180,19 +203,24 @@ export function registerGChatCommands(program: Command): void {
180
203
  profile
181
204
  .command('add')
182
205
  .description('Add a new Google Chat profile (webhook or OAuth)')
183
- .option('--profile <name>', 'Profile name', 'default')
206
+ .option('--profile <name>', 'Profile name (required for webhook, auto-detected for OAuth)')
184
207
  .action(async (options) => {
185
208
  try {
186
- const profileName = await resolveProfileName('gchat', options.profile);
187
-
188
209
  console.error('\nGoogle Chat Setup\n');
189
210
 
190
211
  const profileType = await prompt('Choose profile type (webhook/oauth): ');
191
212
 
192
213
  if (profileType.toLowerCase() === 'webhook') {
193
- await setupWebhookProfile(profileName);
214
+ if (!options.profile) {
215
+ throw new CliError(
216
+ 'INVALID_PARAMS',
217
+ 'Profile name is required for webhook profiles',
218
+ 'Run: agentio gchat profile add --profile <name>'
219
+ );
220
+ }
221
+ await setupWebhookProfile(options.profile);
194
222
  } else if (profileType.toLowerCase() === 'oauth') {
195
- await setupOAuthProfile(profileName);
223
+ await setupOAuthProfile(options.profile);
196
224
  } else {
197
225
  throw new CliError('INVALID_PARAMS', 'Profile type must be "webhook" or "oauth"');
198
226
  }
@@ -257,27 +285,46 @@ async function setupWebhookProfile(profileName: string): Promise<void> {
257
285
  printProfileSetupSuccess(profileName, 'webhook');
258
286
  }
259
287
 
260
- async function setupOAuthProfile(profileName: string): Promise<void> {
288
+ async function setupOAuthProfile(profileNameOverride?: string): Promise<void> {
261
289
  console.error('OAuth Setup\n');
262
290
  console.error('Starting OAuth flow for Google Chat profile...\n');
263
291
 
264
292
  const tokens = await performOAuthFlow('gchat');
293
+ const auth = createGoogleAuth(tokens);
265
294
 
266
- // Optionally fetch user info - Chat API doesn't have a getProfile like Gmail
267
- // For now, just validate the token works
295
+ // Fetch user email for profile naming
296
+ let userEmail: string;
297
+ try {
298
+ const oauth2 = google.oauth2({ version: 'v2', auth });
299
+ const userInfo = await oauth2.userinfo.get();
300
+ userEmail = userInfo.data.email || '';
301
+ if (!userEmail) {
302
+ throw new Error('No email returned');
303
+ }
304
+ } catch (error) {
305
+ const errorMessage = error instanceof Error ? error.message : String(error);
306
+ throw new CliError(
307
+ 'AUTH_FAILED',
308
+ `Failed to fetch user email: ${errorMessage}`,
309
+ 'Ensure the account has an email address'
310
+ );
311
+ }
312
+
313
+ // Validate the token works with Chat API
268
314
  try {
269
- const auth = createGoogleAuth(tokens);
270
315
  const chat = google.chat({ version: 'v1', auth });
271
- // Simple validation: list spaces
272
316
  await chat.spaces.list({ pageSize: 1 });
273
317
  } catch (error) {
318
+ const errorMessage = error instanceof Error ? error.message : String(error);
274
319
  throw new CliError(
275
320
  'AUTH_FAILED',
276
- 'Failed to validate Google Chat access. Check OAuth scopes.',
277
- 'Try again with: agentio gchat profile add --profile ' + profileName
321
+ `Failed to validate Google Chat access: ${errorMessage}`,
322
+ 'Google Chat API requires a Google Workspace account. Personal Gmail accounts cannot use the Chat API.'
278
323
  );
279
324
  }
280
325
 
326
+ const profileName = profileNameOverride || userEmail;
327
+
281
328
  const credentials: GChatOAuthCredentials = {
282
329
  type: 'oauth',
283
330
  accessToken: tokens.access_token,
@@ -285,6 +332,7 @@ async function setupOAuthProfile(profileName: string): Promise<void> {
285
332
  expiryDate: tokens.expiry_date,
286
333
  tokenType: tokens.token_type,
287
334
  scope: tokens.scope,
335
+ email: userEmail,
288
336
  };
289
337
 
290
338
  await setProfile('gchat', profileName);
@@ -6,18 +6,15 @@ import { GitHubClient } from '../services/github/client';
6
6
  import { performGitHubOAuthFlow } from '../auth/github-oauth';
7
7
  import { generateExportData } from './config';
8
8
  import { CliError, handleError } from '../utils/errors';
9
- import { resolveProfileName } from '../utils/stdin';
10
9
  import type { GitHubCredentials } from '../types/github';
11
10
 
12
- async function getGitHubClient(profileName?: string): Promise<{ client: GitHubClient; profile: string }> {
11
+ async function getGitHubClient(profileName: string): Promise<{ client: GitHubClient; profile: string }> {
13
12
  const profile = await getProfile('github', profileName);
14
13
 
15
14
  if (!profile) {
16
15
  throw new CliError(
17
16
  'PROFILE_NOT_FOUND',
18
- profileName
19
- ? `Profile "${profileName}" not found for github`
20
- : 'No default profile configured for github',
17
+ `Profile "${profileName}" not found for github`,
21
18
  'Run: agentio github profile add'
22
19
  );
23
20
  }
@@ -59,7 +56,7 @@ export function registerGitHubCommands(program: Command): void {
59
56
  .command('install')
60
57
  .description('Install AGENTIO_KEY and AGENTIO_CONFIG as GitHub Actions secrets')
61
58
  .argument('<repo>', 'Repository in owner/repo format')
62
- .option('--profile <name>', 'Profile name')
59
+ .requiredOption('--profile <name>', 'Profile name')
63
60
  .action(async (repo: string, options) => {
64
61
  try {
65
62
  // Validate repo format
@@ -94,7 +91,7 @@ export function registerGitHubCommands(program: Command): void {
94
91
  .command('uninstall')
95
92
  .description('Remove AGENTIO_KEY and AGENTIO_CONFIG secrets from a repository')
96
93
  .argument('<repo>', 'Repository in owner/repo format')
97
- .option('--profile <name>', 'Profile name')
94
+ .requiredOption('--profile <name>', 'Profile name')
98
95
  .action(async (repo: string, options) => {
99
96
  try {
100
97
  // Validate repo format
@@ -128,11 +125,9 @@ export function registerGitHubCommands(program: Command): void {
128
125
  profile
129
126
  .command('add')
130
127
  .description('Add a new GitHub profile')
131
- .option('--profile <name>', 'Profile name', 'default')
128
+ .option('--profile <name>', 'Profile name (auto-detected from username if not provided)')
132
129
  .action(async (options) => {
133
130
  try {
134
- const profileName = await resolveProfileName('github', options.profile);
135
-
136
131
  console.error('\nGitHub Setup\n');
137
132
  console.error('This will open your browser to authorize agentio with GitHub.');
138
133
  console.error('You will need to grant access to repositories where you want to set secrets.\n');
@@ -154,6 +149,8 @@ export function registerGitHubCommands(program: Command): void {
154
149
  credentials.username = user.login;
155
150
  credentials.email = user.email;
156
151
 
152
+ const profileName = options.profile || user.login;
153
+
157
154
  console.error(`\nAuthenticated as: ${user.login}${user.email ? ` (${user.email})` : ''}`);
158
155
 
159
156
  // Save credentials
@@ -10,7 +10,7 @@ import { performOAuthFlow } from '../auth/oauth';
10
10
  import { GmailClient } from '../services/gmail/client';
11
11
  import { printMessageList, printMessage, printSendResult, printArchived, printMarked, printAttachmentList, printAttachmentDownloaded, raw } from '../utils/output';
12
12
  import { CliError, handleError } from '../utils/errors';
13
- import { readStdin, resolveProfileName } from '../utils/stdin';
13
+ import { readStdin } from '../utils/stdin';
14
14
  import type { GmailAttachment } from '../types/gmail';
15
15
 
16
16
  function escapeHtml(text: string): string {
@@ -68,7 +68,7 @@ function findChromePath(): string | null {
68
68
  return null;
69
69
  }
70
70
 
71
- async function getGmailClient(profileName?: string): Promise<{ client: GmailClient; profile: string }> {
71
+ async function getGmailClient(profileName: string): Promise<{ client: GmailClient; profile: string }> {
72
72
  const { tokens, profile } = await getValidTokens('gmail', profileName);
73
73
  const auth = createGoogleAuth(tokens);
74
74
  return { client: new GmailClient(auth), profile };
@@ -82,7 +82,7 @@ export function registerGmailCommands(program: Command): void {
82
82
  gmail
83
83
  .command('list')
84
84
  .description('List messages')
85
- .option('--profile <name>', 'Profile name')
85
+ .requiredOption('--profile <name>', 'Profile name')
86
86
  .option('--limit <n>', 'Number of messages', '10')
87
87
  .option('--query <query>', 'Gmail search query (see "gmail search --help" for syntax)')
88
88
  .option('--label <label>', 'Filter by label (repeatable)', (val, acc: string[]) => [...acc, val], [])
@@ -103,7 +103,7 @@ export function registerGmailCommands(program: Command): void {
103
103
  gmail
104
104
  .command('get <message-id>')
105
105
  .description('Get a message')
106
- .option('--profile <name>', 'Profile name')
106
+ .requiredOption('--profile <name>', 'Profile name')
107
107
  .option('--format <format>', 'Body format: text, html, or raw', 'text')
108
108
  .option('--body-only', 'Output only the message body')
109
109
  .action(async (messageId: string, options) => {
@@ -124,7 +124,7 @@ export function registerGmailCommands(program: Command): void {
124
124
  .command('search')
125
125
  .description('Search messages using Gmail query syntax')
126
126
  .requiredOption('--query <query>', 'Search query')
127
- .option('--profile <name>', 'Profile name')
127
+ .requiredOption('--profile <name>', 'Profile name')
128
128
  .option('--limit <n>', 'Max results', '10')
129
129
  .addHelpText('after', `
130
130
  Query Syntax Examples:
@@ -178,7 +178,7 @@ Query Syntax Examples:
178
178
  gmail
179
179
  .command('send')
180
180
  .description('Send an email')
181
- .option('--profile <name>', 'Profile name')
181
+ .requiredOption('--profile <name>', 'Profile name')
182
182
  .requiredOption('--to <email>', 'Recipient (repeatable)', (val, acc: string[]) => [...acc, val], [])
183
183
  .option('--cc <email>', 'CC recipient (repeatable)', (val, acc: string[]) => [...acc, val], [])
184
184
  .option('--bcc <email>', 'BCC recipient (repeatable)', (val, acc: string[]) => [...acc, val], [])
@@ -246,7 +246,7 @@ Query Syntax Examples:
246
246
  gmail
247
247
  .command('reply')
248
248
  .description('Reply to a thread')
249
- .option('--profile <name>', 'Profile name')
249
+ .requiredOption('--profile <name>', 'Profile name')
250
250
  .requiredOption('--thread-id <id>', 'Thread ID')
251
251
  .option('--body <body>', 'Reply body (or pipe via stdin)')
252
252
  .option('--html', 'Treat body as HTML')
@@ -277,7 +277,7 @@ Query Syntax Examples:
277
277
  gmail
278
278
  .command('archive <message-id...>')
279
279
  .description('Archive one or more messages')
280
- .option('--profile <name>', 'Profile name')
280
+ .requiredOption('--profile <name>', 'Profile name')
281
281
  .action(async (messageIds: string[], options) => {
282
282
  try {
283
283
  const { client } = await getGmailClient(options.profile);
@@ -293,7 +293,7 @@ Query Syntax Examples:
293
293
  gmail
294
294
  .command('mark <message-id...>')
295
295
  .description('Mark one or more messages as read or unread')
296
- .option('--profile <name>', 'Profile name')
296
+ .requiredOption('--profile <name>', 'Profile name')
297
297
  .option('--read', 'Mark as read')
298
298
  .option('--unread', 'Mark as unread')
299
299
  .action(async (messageIds: string[], options) => {
@@ -318,7 +318,7 @@ Query Syntax Examples:
318
318
  gmail
319
319
  .command('attachment <message-id>')
320
320
  .description('Download attachments from a message')
321
- .option('--profile <name>', 'Profile name')
321
+ .requiredOption('--profile <name>', 'Profile name')
322
322
  .option('--name <filename>', 'Download specific attachment by filename (downloads all if not specified)')
323
323
  .option('--output <dir>', 'Output directory', '.')
324
324
  .action(async (messageId: string, options) => {
@@ -361,7 +361,7 @@ Query Syntax Examples:
361
361
  gmail
362
362
  .command('export <message-id>')
363
363
  .description('Export a message as PDF')
364
- .option('--profile <name>', 'Profile name')
364
+ .requiredOption('--profile <name>', 'Profile name')
365
365
  .option('--output <path>', 'Output file path', 'message.pdf')
366
366
  .action(async (messageId: string, options) => {
367
367
  try {
@@ -452,12 +452,10 @@ ${emailHeader}
452
452
  profile
453
453
  .command('add')
454
454
  .description('Add a new Gmail profile')
455
- .option('--profile <name>', 'Profile name', 'default')
455
+ .option('--profile <name>', 'Profile name (auto-detected from email if not provided)')
456
456
  .action(async (options) => {
457
457
  try {
458
- const profileName = await resolveProfileName('gmail', options.profile);
459
-
460
- console.error(`Starting OAuth flow for Gmail profile "${profileName}"...`);
458
+ console.error('Starting OAuth flow for Gmail...\n');
461
459
 
462
460
  const tokens = await performOAuthFlow('gmail');
463
461
 
@@ -467,13 +465,17 @@ ${emailHeader}
467
465
  const userProfile = await gmailApi.users.getProfile({ userId: 'me' });
468
466
  const email = userProfile.data.emailAddress;
469
467
 
468
+ if (!email) {
469
+ throw new CliError('AUTH_FAILED', 'Could not fetch email from Gmail', 'Try again or specify --profile manually');
470
+ }
471
+
472
+ const profileName = options.profile || email;
473
+
470
474
  await setProfile('gmail', profileName);
471
475
  await setCredentials('gmail', profileName, { ...tokens, email });
472
476
 
473
- console.log(`\nSuccess! Profile "${profileName}" for Gmail is now configured.`);
474
- if (email) {
475
- console.log(` Email: ${email}`);
476
- }
477
+ console.log(`\nSuccess! Profile "${profileName}" configured.`);
478
+ console.log(` Email: ${email}`);
477
479
  } catch (error) {
478
480
  handleError(error);
479
481
  }
@@ -5,7 +5,7 @@ import { createProfileCommands } from '../utils/profile-commands';
5
5
  import { performJiraOAuthFlow, refreshJiraToken, type AtlassianSite } from '../auth/jira-oauth';
6
6
  import { JiraClient } from '../services/jira/client';
7
7
  import { CliError, handleError } from '../utils/errors';
8
- import { readStdin, prompt, resolveProfileName } from '../utils/stdin';
8
+ import { readStdin, prompt } from '../utils/stdin';
9
9
  import {
10
10
  printJiraProjectList,
11
11
  printJiraIssueList,
@@ -46,15 +46,13 @@ async function ensureValidToken(credentials: JiraCredentials, profile: string):
46
46
  return credentials;
47
47
  }
48
48
 
49
- async function getJiraClient(profileName?: string): Promise<{ client: JiraClient; profile: string }> {
49
+ async function getJiraClient(profileName: string): Promise<{ client: JiraClient; profile: string }> {
50
50
  const profile = await getProfile('jira', profileName);
51
51
 
52
52
  if (!profile) {
53
53
  throw new CliError(
54
54
  'PROFILE_NOT_FOUND',
55
- profileName
56
- ? `Profile "${profileName}" not found for jira`
57
- : 'No default profile configured for jira',
55
+ `Profile "${profileName}" not found for jira`,
58
56
  'Run: agentio jira profile add'
59
57
  );
60
58
  }
@@ -87,7 +85,7 @@ export function registerJiraCommands(program: Command): void {
87
85
  jira
88
86
  .command('projects')
89
87
  .description('List JIRA projects')
90
- .option('--profile <name>', 'Profile name')
88
+ .requiredOption('--profile <name>', 'Profile name')
91
89
  .option('--limit <number>', 'Maximum number of projects', '50')
92
90
  .action(async (options) => {
93
91
  try {
@@ -105,7 +103,7 @@ export function registerJiraCommands(program: Command): void {
105
103
  jira
106
104
  .command('search')
107
105
  .description('Search JIRA issues')
108
- .option('--profile <name>', 'Profile name')
106
+ .requiredOption('--profile <name>', 'Profile name')
109
107
  .option('--jql <query>', 'JQL query')
110
108
  .option('--project <key>', 'Project key')
111
109
  .option('--status <status>', 'Issue status')
@@ -132,7 +130,7 @@ export function registerJiraCommands(program: Command): void {
132
130
  .command('get')
133
131
  .description('Get JIRA issue details')
134
132
  .argument('<issue-key>', 'Issue key (e.g., PROJ-123)')
135
- .option('--profile <name>', 'Profile name')
133
+ .requiredOption('--profile <name>', 'Profile name')
136
134
  .action(async (issueKey: string, options) => {
137
135
  try {
138
136
  const { client } = await getJiraClient(options.profile);
@@ -149,7 +147,7 @@ export function registerJiraCommands(program: Command): void {
149
147
  .description('Add a comment to an issue')
150
148
  .argument('<issue-key>', 'Issue key (e.g., PROJ-123)')
151
149
  .argument('[body]', 'Comment body (or pipe via stdin)')
152
- .option('--profile <name>', 'Profile name')
150
+ .requiredOption('--profile <name>', 'Profile name')
153
151
  .action(async (issueKey: string, body: string | undefined, options) => {
154
152
  try {
155
153
  let text = body;
@@ -175,7 +173,7 @@ export function registerJiraCommands(program: Command): void {
175
173
  .command('transitions')
176
174
  .description('List available transitions for an issue')
177
175
  .argument('<issue-key>', 'Issue key (e.g., PROJ-123)')
178
- .option('--profile <name>', 'Profile name')
176
+ .requiredOption('--profile <name>', 'Profile name')
179
177
  .action(async (issueKey: string, options) => {
180
178
  try {
181
179
  const { client } = await getJiraClient(options.profile);
@@ -192,7 +190,7 @@ export function registerJiraCommands(program: Command): void {
192
190
  .description('Transition an issue to a new status')
193
191
  .argument('<issue-key>', 'Issue key (e.g., PROJ-123)')
194
192
  .argument('<transition-id>', 'Transition ID (use "transitions" command to see available)')
195
- .option('--profile <name>', 'Profile name')
193
+ .requiredOption('--profile <name>', 'Profile name')
196
194
  .action(async (issueKey: string, transitionId: string, options) => {
197
195
  try {
198
196
  const { client } = await getJiraClient(options.profile);
@@ -213,11 +211,9 @@ export function registerJiraCommands(program: Command): void {
213
211
  profile
214
212
  .command('add')
215
213
  .description('Add a new JIRA profile with OAuth authentication')
216
- .option('--profile <name>', 'Profile name', 'default')
214
+ .option('--profile <name>', 'Profile name (auto-detected from site URL if not provided)')
217
215
  .action(async (options) => {
218
216
  try {
219
- const profileName = await resolveProfileName('jira', options.profile);
220
-
221
217
  console.error('\nJIRA OAuth Setup\n');
222
218
 
223
219
  // Site selection callback
@@ -242,6 +238,10 @@ export function registerJiraCommands(program: Command): void {
242
238
 
243
239
  console.error(`\nAuthorized for site: ${result.siteUrl}\n`);
244
240
 
241
+ // Auto-name based on site hostname
242
+ const siteHostname = new URL(result.siteUrl).hostname;
243
+ const profileName = options.profile || siteHostname;
244
+
245
245
  // Save credentials
246
246
  const credentials: JiraCredentials = {
247
247
  accessToken: result.accessToken,
@@ -5,19 +5,17 @@ import { setProfile, getProfile } from '../config/config-manager';
5
5
  import { createProfileCommands } from '../utils/profile-commands';
6
6
  import { SlackClient } from '../services/slack/client';
7
7
  import { CliError, handleError } from '../utils/errors';
8
- import { readStdin, prompt, resolveProfileName } from '../utils/stdin';
8
+ import { readStdin, prompt } from '../utils/stdin';
9
9
  import { printSlackSendResult } from '../utils/output';
10
10
  import type { SlackCredentials, SlackWebhookCredentials } from '../types/slack';
11
11
 
12
- async function getSlackClient(profileName?: string): Promise<{ client: SlackClient; profile: string }> {
12
+ async function getSlackClient(profileName: string): Promise<{ client: SlackClient; profile: string }> {
13
13
  const profile = await getProfile('slack', profileName);
14
14
 
15
15
  if (!profile) {
16
16
  throw new CliError(
17
17
  'PROFILE_NOT_FOUND',
18
- profileName
19
- ? `Profile "${profileName}" not found for slack`
20
- : 'No default profile configured for slack',
18
+ `Profile "${profileName}" not found for slack`,
21
19
  'Run: agentio slack profile add'
22
20
  );
23
21
  }
@@ -46,7 +44,7 @@ export function registerSlackCommands(program: Command): void {
46
44
  slack
47
45
  .command('send')
48
46
  .description('Send a message to Slack')
49
- .option('--profile <name>', 'Profile name')
47
+ .requiredOption('--profile <name>', 'Profile name')
50
48
  .option('--json [file]', 'Send Block Kit message from JSON file (or stdin if no file specified)')
51
49
  .argument('[message]', 'Message text (or pipe via stdin)')
52
50
  .action(async (message: string | undefined, options) => {
@@ -134,11 +132,10 @@ export function registerSlackCommands(program: Command): void {
134
132
  profile
135
133
  .command('add')
136
134
  .description('Add a new Slack profile (webhook)')
137
- .option('--profile <name>', 'Profile name', 'default')
135
+ .requiredOption('--profile <name>', 'Profile name (required)')
138
136
  .action(async (options) => {
139
137
  try {
140
- const profileName = await resolveProfileName('slack', options.profile);
141
- await setupWebhookProfile(profileName);
138
+ await setupWebhookProfile(options.profile);
142
139
  } catch (error) {
143
140
  handleError(error);
144
141
  }
@@ -4,18 +4,16 @@ import { setProfile, getProfile } from '../config/config-manager';
4
4
  import { createProfileCommands } from '../utils/profile-commands';
5
5
  import { SqlClient } from '../services/sql/client';
6
6
  import { CliError, handleError } from '../utils/errors';
7
- import { readStdin, prompt, resolveProfileName } from '../utils/stdin';
7
+ import { readStdin, prompt } from '../utils/stdin';
8
8
  import type { SqlCredentials } from '../types/sql';
9
9
 
10
- async function getSqlClient(profileName?: string): Promise<{ client: SqlClient; profile: string }> {
10
+ async function getSqlClient(profileName: string): Promise<{ client: SqlClient; profile: string }> {
11
11
  const profile = await getProfile('sql', profileName);
12
12
 
13
13
  if (!profile) {
14
14
  throw new CliError(
15
15
  'PROFILE_NOT_FOUND',
16
- profileName
17
- ? `Profile "${profileName}" not found for sql`
18
- : 'No default profile configured for sql',
16
+ `Profile "${profileName}" not found for sql`,
19
17
  'Run: agentio sql profile add'
20
18
  );
21
19
  }
@@ -55,7 +53,7 @@ export function registerSqlCommands(program: Command): void {
55
53
  sql
56
54
  .command('query')
57
55
  .description('Execute a SQL query')
58
- .option('--profile <name>', 'Profile name')
56
+ .requiredOption('--profile <name>', 'Profile name')
59
57
  .option('--limit <n>', 'Maximum rows to return', '100')
60
58
  .argument('[query]', 'SQL query (or pipe via stdin)')
61
59
  .action(async (query: string | undefined, options) => {
@@ -94,12 +92,10 @@ export function registerSqlCommands(program: Command): void {
94
92
  profile
95
93
  .command('add')
96
94
  .description('Add a new SQL database profile')
97
- .option('--profile <name>', 'Profile name', 'default')
95
+ .option('--profile <name>', 'Profile name (auto-detected from connection if not provided)')
98
96
  .option('--interactive', 'Interactive mode: prompt for individual connection components')
99
97
  .action(async (options) => {
100
98
  try {
101
- const profileName = await resolveProfileName('sql', options.profile);
102
-
103
99
  let url: string;
104
100
 
105
101
  if (options.interactive) {
@@ -138,6 +134,9 @@ export function registerSqlCommands(program: Command): void {
138
134
  const displayName = extractDisplayName(url);
139
135
  console.error(`\nConnected to: ${displayName}\n`);
140
136
 
137
+ // Auto-name based on connection display name
138
+ const profileName = options.profile || displayName;
139
+
141
140
  // Save credentials
142
141
  const credentials: SqlCredentials = {
143
142
  url,
@@ -144,7 +144,6 @@ async function createServiceClient(
144
144
  interface ProfileStatus {
145
145
  service: string;
146
146
  profile: string;
147
- isDefault: boolean;
148
147
  status: 'ok' | 'invalid' | 'no-creds' | 'skipped';
149
148
  info?: string;
150
149
  error?: string;
@@ -166,7 +165,7 @@ export function registerStatusCommand(program: Command): void {
166
165
  // Collect all profile statuses
167
166
  const statuses: ProfileStatus[] = [];
168
167
 
169
- for (const { service, profiles, default: defaultProfile } of allProfiles) {
168
+ for (const { service, profiles } of allProfiles) {
170
169
  for (const name of profiles) {
171
170
  const credentials = await getCredentials(service, name);
172
171
 
@@ -174,7 +173,6 @@ export function registerStatusCommand(program: Command): void {
174
173
  statuses.push({
175
174
  service,
176
175
  profile: name,
177
- isDefault: name === defaultProfile,
178
176
  status: 'no-creds',
179
177
  });
180
178
  continue;
@@ -184,7 +182,6 @@ export function registerStatusCommand(program: Command): void {
184
182
  statuses.push({
185
183
  service,
186
184
  profile: name,
187
- isDefault: name === defaultProfile,
188
185
  status: 'skipped',
189
186
  });
190
187
  continue;
@@ -202,7 +199,6 @@ export function registerStatusCommand(program: Command): void {
202
199
  statuses.push({
203
200
  service,
204
201
  profile: name,
205
- isDefault: name === defaultProfile,
206
202
  status: result.valid ? 'ok' : 'invalid',
207
203
  info: result.info,
208
204
  error: result.error,
@@ -244,13 +240,12 @@ export function registerStatusCommand(program: Command): void {
244
240
 
245
241
  // Calculate column widths
246
242
  const serviceWidth = Math.max(...statuses.map((s) => s.service.length));
247
- const profileWidth = Math.max(...statuses.map((s) => s.profile.length + (s.isDefault ? 1 : 0)));
243
+ const profileWidth = Math.max(...statuses.map((s) => s.profile.length));
248
244
 
249
245
  // Print each profile on one line
250
246
  for (const s of statuses) {
251
247
  const servicePad = s.service.padEnd(serviceWidth);
252
- const profileName = s.profile + (s.isDefault ? '*' : '');
253
- const profilePad = profileName.padEnd(profileWidth);
248
+ const profilePad = s.profile.padEnd(profileWidth);
254
249
 
255
250
  let statusStr: string;
256
251
  let details: string;
@@ -4,18 +4,16 @@ import { setProfile, getProfile } from '../config/config-manager';
4
4
  import { createProfileCommands } from '../utils/profile-commands';
5
5
  import { TelegramClient } from '../services/telegram/client';
6
6
  import { CliError, handleError } from '../utils/errors';
7
- import { readStdin, prompt, resolveProfileName } from '../utils/stdin';
7
+ import { readStdin, prompt } from '../utils/stdin';
8
8
  import type { TelegramCredentials, TelegramSendOptions } from '../types/telegram';
9
9
 
10
- async function getTelegramClient(profileName?: string): Promise<{ client: TelegramClient; profile: string }> {
10
+ async function getTelegramClient(profileName: string): Promise<{ client: TelegramClient; profile: string }> {
11
11
  const profile = await getProfile('telegram', profileName);
12
12
 
13
13
  if (!profile) {
14
14
  throw new CliError(
15
15
  'PROFILE_NOT_FOUND',
16
- profileName
17
- ? `Profile "${profileName}" not found for telegram`
18
- : 'No default profile configured for telegram',
16
+ `Profile "${profileName}" not found for telegram`,
19
17
  'Run: agentio telegram profile add'
20
18
  );
21
19
  }
@@ -44,7 +42,7 @@ export function registerTelegramCommands(program: Command): void {
44
42
  telegram
45
43
  .command('send')
46
44
  .description('Send a message to the channel')
47
- .option('--profile <name>', 'Profile name')
45
+ .requiredOption('--profile <name>', 'Profile name')
48
46
  .option('--parse-mode <mode>', 'Message format: html or markdown')
49
47
  .option('--silent', 'Send without notification')
50
48
  .argument('[message]', 'Message text (or pipe via stdin)')
@@ -92,11 +90,9 @@ export function registerTelegramCommands(program: Command): void {
92
90
  profile
93
91
  .command('add')
94
92
  .description('Add a new Telegram bot profile')
95
- .option('--profile <name>', 'Profile name', 'default')
93
+ .option('--profile <name>', 'Profile name (auto-detected from bot username if not provided)')
96
94
  .action(async (options) => {
97
95
  try {
98
- const profileName = await resolveProfileName('telegram', options.profile);
99
-
100
96
  console.error('\nTelegram Bot Setup\n');
101
97
 
102
98
  // Step 1: Create bot
@@ -174,6 +170,9 @@ export function registerTelegramCommands(program: Command): void {
174
170
  console.error(' /setuserpic - Set bot photo');
175
171
  console.error(' /setdescription - Set bot description\n');
176
172
 
173
+ // Auto-name based on bot username
174
+ const profileName = options.profile || botInfo.username;
175
+
177
176
  // Save credentials
178
177
  const credentials: TelegramCredentials = {
179
178
  bot_token: botToken,
@@ -11,7 +11,6 @@ const ALL_SERVICES: ServiceName[] = ['gmail', 'gchat', 'jira', 'slack', 'telegra
11
11
 
12
12
  const DEFAULT_CONFIG: Config = {
13
13
  profiles: {},
14
- defaults: {},
15
14
  };
16
15
 
17
16
  export async function ensureConfigDir(): Promise<void> {
@@ -50,21 +49,16 @@ export async function saveConfig(config: Config): Promise<void> {
50
49
 
51
50
  export async function getProfile(
52
51
  service: ServiceName,
53
- profileName?: string
52
+ profileName: string
54
53
  ): Promise<string | null> {
55
54
  const config = await loadConfig();
56
- const name = profileName || config.defaults[service];
57
-
58
- if (!name) {
59
- return null;
60
- }
61
55
 
62
56
  const serviceProfiles = config.profiles[service] || [];
63
- if (!serviceProfiles.includes(name)) {
57
+ if (!serviceProfiles.includes(profileName)) {
64
58
  return null;
65
59
  }
66
60
 
67
- return name;
61
+ return profileName;
68
62
  }
69
63
 
70
64
  export async function setProfile(
@@ -81,11 +75,6 @@ export async function setProfile(
81
75
  config.profiles[service]!.push(profileName);
82
76
  }
83
77
 
84
- // Set as default if it's the first profile for this service
85
- if (!config.defaults[service]) {
86
- config.defaults[service] = profileName;
87
- }
88
-
89
78
  await saveConfig(config);
90
79
  }
91
80
 
@@ -102,27 +91,6 @@ export async function removeProfile(
102
91
 
103
92
  config.profiles[service] = serviceProfiles.filter((p) => p !== profileName);
104
93
 
105
- // Clear default if it was the removed profile
106
- if (config.defaults[service] === profileName) {
107
- config.defaults[service] = config.profiles[service]![0];
108
- }
109
-
110
- await saveConfig(config);
111
- return true;
112
- }
113
-
114
- export async function setDefault(
115
- service: ServiceName,
116
- profileName: string
117
- ): Promise<boolean> {
118
- const config = await loadConfig();
119
-
120
- const serviceProfiles = config.profiles[service] || [];
121
- if (!serviceProfiles.includes(profileName)) {
122
- return false;
123
- }
124
-
125
- config.defaults[service] = profileName;
126
94
  await saveConfig(config);
127
95
  return true;
128
96
  }
@@ -130,7 +98,6 @@ export async function setDefault(
130
98
  export async function listProfiles(service?: ServiceName): Promise<{
131
99
  service: ServiceName;
132
100
  profiles: string[];
133
- default?: string;
134
101
  }[]> {
135
102
  const config = await loadConfig();
136
103
  const services = service ? [service] : ALL_SERVICES;
@@ -138,7 +105,6 @@ export async function listProfiles(service?: ServiceName): Promise<{
138
105
  return services.map((svc) => ({
139
106
  service: svc,
140
107
  profiles: config.profiles[svc] || [],
141
- default: config.defaults[svc],
142
108
  }));
143
109
  }
144
110
 
@@ -13,6 +13,7 @@ import type {
13
13
  GChatWebhookCredentials,
14
14
  GChatOAuthCredentials,
15
15
  GChatMessage,
16
+ GChatSpace,
16
17
  } from '../../types/gchat';
17
18
 
18
19
  export class GChatClient implements ServiceClient {
@@ -87,6 +88,17 @@ export class GChatClient implements ServiceClient {
87
88
  return this.getViaOAuth(options);
88
89
  }
89
90
 
91
+ async listSpaces(): Promise<GChatSpace[]> {
92
+ if (this.credentials.type === 'webhook') {
93
+ throw new CliError(
94
+ 'PERMISSION_DENIED',
95
+ 'Listing spaces is not supported for webhook profiles',
96
+ 'Use an OAuth profile to list spaces'
97
+ );
98
+ }
99
+ return this.listSpacesViaOAuth();
100
+ }
101
+
90
102
  private async sendViaWebhook(options: GChatSendOptions): Promise<GChatSendResult> {
91
103
  const webhookUrl = (this.credentials as GChatWebhookCredentials).webhookUrl;
92
104
 
@@ -194,9 +206,21 @@ export class GChatClient implements ServiceClient {
194
206
  const chat = google.chat({ version: 'v1', auth });
195
207
 
196
208
  try {
209
+ // Build filter from options
210
+ const filters: string[] = [];
211
+ if (options.threadId) {
212
+ filters.push(`thread.name = "spaces/${options.spaceId}/threads/${options.threadId}"`);
213
+ }
214
+ if (options.since) {
215
+ filters.push(`createTime > "${options.since.toISOString()}"`);
216
+ }
217
+ const filter = filters.length > 0 ? filters.join(' AND ') : undefined;
218
+
197
219
  const response = await chat.spaces.messages.list({
198
220
  parent: `spaces/${options.spaceId}`,
199
221
  pageSize: options.limit || 10,
222
+ orderBy: 'createTime desc',
223
+ filter,
200
224
  });
201
225
 
202
226
  const messages = response.data.messages || [];
@@ -277,6 +301,32 @@ export class GChatClient implements ServiceClient {
277
301
  }
278
302
  }
279
303
 
304
+ private async listSpacesViaOAuth(): Promise<GChatSpace[]> {
305
+ const oauthCreds = this.credentials as GChatOAuthCredentials;
306
+ const auth = this.createOAuthClient(oauthCreds);
307
+ const chat = google.chat({ version: 'v1', auth });
308
+
309
+ try {
310
+ const response = await chat.spaces.list({});
311
+
312
+ const spaces = response.data.spaces || [];
313
+ return spaces.map((space: chat_v1.Schema$Space) => ({
314
+ name: space.name || '',
315
+ displayName: space.displayName || 'Unnamed',
316
+ type: (space.type as 'ROOM' | 'DM') || 'ROOM',
317
+ description: space.spaceDetails?.description || undefined,
318
+ }));
319
+ } catch (err) {
320
+ const code = this.getErrorCode(err);
321
+ const message = this.getErrorMessage(err);
322
+ throw new CliError(
323
+ code,
324
+ `Failed to list spaces: ${message}`,
325
+ 'Check that OAuth token is valid and has Chat scope'
326
+ );
327
+ }
328
+ }
329
+
280
330
  private getErrorCode(err: unknown): ErrorCode {
281
331
  if (err && typeof err === 'object') {
282
332
  const error = err as Record<string, unknown>;
@@ -9,16 +9,6 @@ export interface Config {
9
9
  discourse?: string[];
10
10
  sql?: string[];
11
11
  };
12
- defaults: {
13
- gmail?: string;
14
- gchat?: string;
15
- github?: string;
16
- jira?: string;
17
- slack?: string;
18
- telegram?: string;
19
- discourse?: string;
20
- sql?: string;
21
- };
22
12
  env?: Record<string, string>;
23
13
  }
24
14
 
@@ -42,6 +42,7 @@ export interface GChatOAuthCredentials {
42
42
  expiryDate?: number;
43
43
  tokenType: string;
44
44
  scope?: string;
45
+ email: string;
45
46
  }
46
47
 
47
48
  export type GChatCredentials = GChatWebhookCredentials | GChatOAuthCredentials;
@@ -55,6 +56,8 @@ export interface GChatSendOptions {
55
56
  export interface GChatListOptions {
56
57
  spaceId: string;
57
58
  limit?: number;
59
+ threadId?: string;
60
+ since?: Date;
58
61
  }
59
62
 
60
63
  export interface GChatGetOptions {
@@ -1,5 +1,5 @@
1
1
  import type { GmailMessage, GmailAttachmentInfo } from '../types/gmail';
2
- import type { GChatMessage } from '../types/gchat';
2
+ import type { GChatMessage, GChatSpace } from '../types/gchat';
3
3
  import type { JiraProject, JiraIssue, JiraTransition, JiraCommentResult, JiraTransitionResult } from '../types/jira';
4
4
  import type { SlackSendResult } from '../types/slack';
5
5
  import type { RssFeed, RssArticle } from '../types/rss';
@@ -147,6 +147,25 @@ export function printGChatMessage(msg: GChatMessage): void {
147
147
  }
148
148
  }
149
149
 
150
+ export function printGChatSpaceList(spaces: GChatSpace[]): void {
151
+ if (spaces.length === 0) {
152
+ console.log('No spaces found');
153
+ return;
154
+ }
155
+
156
+ console.log(`Spaces (${spaces.length})\n`);
157
+
158
+ for (const space of spaces) {
159
+ const spaceId = space.name.replace('spaces/', '');
160
+ console.log(`[${space.type}] ${space.displayName}`);
161
+ console.log(` ID: ${spaceId}`);
162
+ if (space.description) {
163
+ console.log(` Description: ${space.description}`);
164
+ }
165
+ console.log('');
166
+ }
167
+ }
168
+
150
169
  // JIRA specific formatters
151
170
  export function printJiraProjectList(projects: JiraProject[]): void {
152
171
  if (projects.length === 0) {
@@ -1,7 +1,7 @@
1
1
  import { Command } from 'commander';
2
- import { listProfiles, removeProfile, setDefault } from '../config/config-manager';
2
+ import { listProfiles, removeProfile } from '../config/config-manager';
3
3
  import { removeCredentials, getCredentials } from '../auth/token-store';
4
- import { handleError, CliError } from './errors';
4
+ import { handleError } from './errors';
5
5
  import type { ServiceName } from '../types/config';
6
6
 
7
7
  export interface ProfileCommandsOptions<T> {
@@ -26,16 +26,15 @@ export function createProfileCommands<T>(
26
26
  .action(async () => {
27
27
  try {
28
28
  const result = await listProfiles(service);
29
- const { profiles, default: defaultProfile } = result[0];
29
+ const { profiles } = result[0];
30
30
 
31
31
  if (profiles.length === 0) {
32
32
  console.log('No profiles configured');
33
33
  } else {
34
34
  for (const name of profiles) {
35
- const marker = name === defaultProfile ? ' (default)' : '';
36
35
  const credentials = await getCredentials<T>(service, name);
37
36
  const extraInfo = getExtraInfo ? getExtraInfo(credentials) : '';
38
- console.log(`${name}${marker}${extraInfo}`);
37
+ console.log(`${name}${extraInfo}`);
39
38
  }
40
39
  }
41
40
  } catch (error) {
@@ -64,27 +63,5 @@ export function createProfileCommands<T>(
64
63
  }
65
64
  });
66
65
 
67
- profile
68
- .command('default')
69
- .description(`Set the default ${displayName} profile`)
70
- .argument('<name>', 'Profile name to set as default')
71
- .action(async (name) => {
72
- try {
73
- const success = await setDefault(service, name);
74
-
75
- if (success) {
76
- console.log(`Default profile set to "${name}"`);
77
- } else {
78
- throw new CliError(
79
- 'PROFILE_NOT_FOUND',
80
- `Profile "${name}" not found`,
81
- `Run: agentio ${service} profile list`
82
- );
83
- }
84
- } catch (error) {
85
- handleError(error);
86
- }
87
- });
88
-
89
66
  return profile;
90
67
  }
@@ -1,6 +1,4 @@
1
1
  import { createInterface } from 'readline';
2
- import { getProfile } from '../config/config-manager';
3
- import type { ServiceName } from '../types/config';
4
2
 
5
3
  /**
6
4
  * Prompt the user for input with a question.
@@ -27,40 +25,6 @@ export async function confirm(question: string): Promise<boolean> {
27
25
  return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
28
26
  }
29
27
 
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
-
64
28
  export async function readStdin(): Promise<string | null> {
65
29
  // Check if stdin is a TTY (interactive terminal)
66
30
  if (process.stdin.isTTY) {