@rui.branco/jira-mcp 1.6.14 → 1.6.16

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 (3) hide show
  1. package/index.js +180 -7
  2. package/package.json +1 -1
  3. package/setup.js +103 -5
package/index.js CHANGED
@@ -184,6 +184,48 @@ async function fetchJiraAgile(endpoint, options = {}, instance = defaultInstance
184
184
  return text ? JSON.parse(text) : {};
185
185
  }
186
186
 
187
+ async function fetchJiraTeams(endpoint, options = {}, instance = defaultInstance) {
188
+ const { method = "GET", body } = options;
189
+ const headers = {
190
+ Authorization: `Basic ${instance.auth}`,
191
+ Accept: "application/json",
192
+ };
193
+ if (body) {
194
+ headers["Content-Type"] = "application/json";
195
+ }
196
+ const response = await fetch(`${instance.baseUrl}/rest/teams/1.0${endpoint}`, {
197
+ method,
198
+ headers,
199
+ body: body ? JSON.stringify(body) : undefined,
200
+ });
201
+ if (!response.ok) {
202
+ const errorBody = await response.text().catch(() => "");
203
+ throw new Error(
204
+ `Jira Teams API error: ${response.status} ${response.statusText}${errorBody ? ` - ${errorBody}` : ""}`,
205
+ );
206
+ }
207
+ const text = await response.text();
208
+ return text ? JSON.parse(text) : {};
209
+ }
210
+
211
+ async function resolveTeamId(teamName, instance) {
212
+ const teams = await fetchJiraTeams(
213
+ `/teams/find?query=${encodeURIComponent(teamName)}&excludeMembers=true`,
214
+ {},
215
+ instance,
216
+ );
217
+ const match = teams.find(
218
+ (t) => t.title.toLowerCase() === teamName.toLowerCase(),
219
+ );
220
+ if (!match) {
221
+ const available = teams.map((t) => t.title).join(", ");
222
+ throw new Error(
223
+ `Team "${teamName}" not found.${available ? ` Similar teams: ${available}` : ""}`,
224
+ );
225
+ }
226
+ return `${match.organizationId}-${match.id}`;
227
+ }
228
+
187
229
  async function downloadAttachment(url, filename, issueKey, instance) {
188
230
  const issueDir = path.join(attachmentDir, issueKey);
189
231
  if (!fs.existsSync(issueDir)) {
@@ -848,6 +890,13 @@ async function getTicket(issueKey, downloadImages = true, fetchFigma = true, ins
848
890
  if (fields.resolutiondate) output += `**Resolved:** ${fields.resolutiondate}\n`;
849
891
  if (fields.resolution) output += `**Resolution:** ${fields.resolution.name}\n`;
850
892
 
893
+ // Team
894
+ if (fields.customfield_10001) {
895
+ const teamVal = fields.customfield_10001;
896
+ const teamName = typeof teamVal === "object" ? teamVal.name || teamVal.title || teamVal.value || JSON.stringify(teamVal) : teamVal;
897
+ output += `**Team:** ${teamName}\n`;
898
+ }
899
+
851
900
  // Story points
852
901
  if (storyPoints != null) output += `**Story Points:** ${storyPoints}\n`;
853
902
 
@@ -1477,7 +1526,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1477
1526
  {
1478
1527
  name: "jira_add_comment",
1479
1528
  description:
1480
- "Add a comment to a Jira ticket. IMPORTANT: Use @DisplayName (e.g. @Julia Pereszta) for mentions NOT [~accountId:...] syntax. Keep comments non-technical and user-facing. Never mention git details like 'pushed to main', branch names, or technical implementation details stakeholders don't care about that. NEVER use em dashes (—) or en dashes (–) in comments — use commas, periods, or rewrite the sentence instead.",
1529
+ "Add a comment to a Jira ticket. IMPORTANT RULES: (1) Write comments at a HIGH LEVEL for stakeholders, product owners, and managers. Focus on WHAT was done and the business impact, NOT how it was done. Example: 'Implemented the new filtering feature for the dashboard' instead of 'Added a useEffect hook with debounced API calls to filter endpoint'. (2) NEVER mention technical details: no code, no function names, no file paths, no git branches, no commit hashes, no 'pushed to main', no framework-specific terms (React, Angular, hooks, components, etc.). (3) Use @DisplayName (e.g. @Julia Pereszta) for mentions, NOT [~accountId:...] syntax. (4) NEVER use em dashes (—) or en dashes (–), use commas, periods, or rewrite the sentence instead.",
1481
1530
  inputSchema: {
1482
1531
  type: "object",
1483
1532
  properties: {
@@ -1646,6 +1695,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1646
1695
  items: { type: "string" },
1647
1696
  description: "Labels to set on the ticket",
1648
1697
  },
1698
+ team: {
1699
+ type: "string",
1700
+ description: "Team name to assign (e.g., 'Site Surveys (MODS)'). Resolved automatically via Jira Teams API.",
1701
+ },
1649
1702
  instance: {
1650
1703
  type: "string",
1651
1704
  description: "Instance name override. Auto-detected from issue key prefix if omitted.",
@@ -1740,6 +1793,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1740
1793
  type: "boolean",
1741
1794
  description: "Set this instance as the default (default: false)",
1742
1795
  },
1796
+ defaultTeam: {
1797
+ type: "string",
1798
+ description: "Default team name to auto-assign when creating tickets (e.g., 'Site Surveys (MODS)'). Resolved and validated via Jira Teams API. Pass empty string to clear.",
1799
+ },
1743
1800
  },
1744
1801
  required: ["name"],
1745
1802
  },
@@ -1827,6 +1884,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1827
1884
  type: "number",
1828
1885
  description: "Sprint ID to add the ticket to. Use jira_get_sprints to find sprint IDs.",
1829
1886
  },
1887
+ team: {
1888
+ type: "string",
1889
+ description: "Team name to assign (e.g., 'Site Surveys (MODS)'). Resolved automatically via Jira Teams API.",
1890
+ },
1830
1891
  instance: {
1831
1892
  type: "string",
1832
1893
  description: "Instance name override. Auto-detected from project key if omitted.",
@@ -1867,6 +1928,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1867
1928
  items: { type: "string" },
1868
1929
  description: "Labels to set on the subtask",
1869
1930
  },
1931
+ team: {
1932
+ type: "string",
1933
+ description: "Team name to assign (e.g., 'Site Surveys (MODS)'). Resolved automatically via Jira Teams API.",
1934
+ },
1870
1935
  instance: {
1871
1936
  type: "string",
1872
1937
  description: "Instance name override. Auto-detected from parent issue key prefix if omitted.",
@@ -2733,6 +2798,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2733
2798
  if (args.labels) {
2734
2799
  fields.labels = args.labels;
2735
2800
  }
2801
+ if (args.team) {
2802
+ fields.customfield_10001 = await resolveTeamId(args.team, inst);
2803
+ }
2736
2804
 
2737
2805
  if (Object.keys(fields).length === 0) {
2738
2806
  return {
@@ -2786,7 +2854,41 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2786
2854
  const projects = args.projects ? args.projects.map((p) => p.toUpperCase()) : (existing.projects || []);
2787
2855
  const authStr = Buffer.from(`${email}:${token}`).toString("base64");
2788
2856
 
2789
- const newInstance = { name: instName, email, token, baseUrl, projects, auth: authStr };
2857
+ // Resolve defaultTeam if provided
2858
+ let defaultTeam = existing.defaultTeam || undefined;
2859
+ if (args.defaultTeam !== undefined) {
2860
+ if (args.defaultTeam === "") {
2861
+ defaultTeam = undefined;
2862
+ } else {
2863
+ // Validate team exists via Jira Teams API
2864
+ const tempInst = { baseUrl, auth: authStr };
2865
+ try {
2866
+ const teamId = await resolveTeamId(args.defaultTeam, tempInst);
2867
+ defaultTeam = { name: args.defaultTeam, id: teamId };
2868
+ } catch (e) {
2869
+ // Try to list available teams for a helpful error
2870
+ try {
2871
+ const teams = await fetchJiraTeams(
2872
+ `/teams/find?query=&excludeMembers=true`,
2873
+ {},
2874
+ tempInst,
2875
+ );
2876
+ const available = teams.map((t) => t.title).join(", ");
2877
+ return {
2878
+ content: [{ type: "text", text: `Team "${args.defaultTeam}" not found. Available teams: ${available}` }],
2879
+ isError: true,
2880
+ };
2881
+ } catch {
2882
+ return {
2883
+ content: [{ type: "text", text: e.message }],
2884
+ isError: true,
2885
+ };
2886
+ }
2887
+ }
2888
+ }
2889
+ }
2890
+
2891
+ const newInstance = { name: instName, email, token, baseUrl, projects, auth: authStr, defaultTeam };
2790
2892
 
2791
2893
  // Update in-memory instances
2792
2894
  if (isUpdate) {
@@ -2818,6 +2920,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2818
2920
 
2819
2921
  // Save without the computed auth field
2820
2922
  const toSave = { name: instName, email, token, baseUrl, projects };
2923
+ if (defaultTeam) toSave.defaultTeam = defaultTeam;
2821
2924
  const savedIdx = savedConfig.instances.findIndex((i) => i.name === instName);
2822
2925
  if (savedIdx >= 0) {
2823
2926
  savedConfig.instances[savedIdx] = toSave;
@@ -2833,6 +2936,23 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2833
2936
  let text = `${action} instance "${instName}" (${baseUrl}).`;
2834
2937
  if (projects.length > 0) text += ` Projects: ${projects.join(", ")}.`;
2835
2938
  if (args.setDefault) text += " Set as default.";
2939
+ if (defaultTeam) {
2940
+ text += ` Default team: ${defaultTeam.name}.`;
2941
+ } else {
2942
+ // No default team — fetch available teams and prompt
2943
+ try {
2944
+ const tempInst = { baseUrl, auth: authStr };
2945
+ const teams = await fetchJiraTeams(
2946
+ `/teams/find?query=&excludeMembers=true`, {}, tempInst,
2947
+ );
2948
+ if (teams && teams.length > 0) {
2949
+ const list = teams.map((t, i) => `${i + 1}. ${t.title}`).join("\n");
2950
+ text += `\n\n⚠ No default team configured. Available teams:\n${list}\n0. None\n\nAsk the user which team to set as default. If they pick one, call jira_add_instance with name="${instName}" and defaultTeam="<team name>".`;
2951
+ }
2952
+ } catch {
2953
+ // Teams API not available, skip
2954
+ }
2955
+ }
2836
2956
 
2837
2957
  return { content: [{ type: "text", text }] };
2838
2958
 
@@ -2877,7 +2997,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2877
2997
  for (const inst of instances) {
2878
2998
  const isDefault = inst.name === currentDefault ? " **(default)**" : "";
2879
2999
  const projs = inst.projects?.length > 0 ? `\n Projects: ${inst.projects.join(", ")}` : "";
2880
- text += `- **${inst.name}**${isDefault}: ${inst.baseUrl} (${inst.email})${projs}\n`;
3000
+ const team = inst.defaultTeam ? `\n Default team: ${inst.defaultTeam.name}` : "";
3001
+ text += `- **${inst.name}**${isDefault}: ${inst.baseUrl} (${inst.email})${projs}${team}\n`;
2881
3002
  }
2882
3003
  return { content: [{ type: "text", text }] };
2883
3004
 
@@ -2921,6 +3042,25 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2921
3042
  if (args.parentKey) {
2922
3043
  fields.parent = { key: args.parentKey };
2923
3044
  }
3045
+ let teamPrompt = "";
3046
+ if (args.team) {
3047
+ fields.customfield_10001 = await resolveTeamId(args.team, inst);
3048
+ } else if (inst.defaultTeam) {
3049
+ fields.customfield_10001 = inst.defaultTeam.id;
3050
+ } else {
3051
+ // No team param and no default — fetch available teams and prompt user
3052
+ try {
3053
+ const teams = await fetchJiraTeams(
3054
+ `/teams/find?query=&excludeMembers=true`, {}, inst,
3055
+ );
3056
+ if (teams && teams.length > 0) {
3057
+ const list = teams.map((t, i) => `${i + 1}. ${t.title}`).join("\n");
3058
+ teamPrompt = `\n\n⚠ No team assigned and no default team configured for instance "${inst.name}". Available teams:\n${list}\n0. None\n\nTo assign a team to this ticket, call jira_update_ticket with issueKey and team parameter.\nIf a team is selected, ask the user if it should also be saved as the default team for instance "${inst.name}" (via jira_add_instance with defaultTeam).`;
3059
+ }
3060
+ } catch {
3061
+ // Teams API not available, proceed without team
3062
+ }
3063
+ }
2924
3064
 
2925
3065
  const result = await fetchJira("/issue", { method: "POST", body: { fields } }, inst);
2926
3066
  const newKey = result.key;
@@ -2935,6 +3075,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2935
3075
 
2936
3076
  let text = `Created ${newKey}: ${args.summary}\nURL: ${inst.baseUrl}/browse/${newKey}`;
2937
3077
  if (args.sprintId) text += `\nAdded to sprint ${args.sprintId}.`;
3078
+ text += teamPrompt;
2938
3079
  return { content: [{ type: "text", text }] };
2939
3080
 
2940
3081
  } else if (name === "jira_create_subtask") {
@@ -2967,6 +3108,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2967
3108
  if (args.labels) {
2968
3109
  fields.labels = args.labels;
2969
3110
  }
3111
+ let teamWarning = "";
3112
+ if (args.team) {
3113
+ fields.customfield_10001 = await resolveTeamId(args.team, inst);
3114
+ } else {
3115
+ // Inherit team from parent ticket
3116
+ const parentIssue = await fetchJira(
3117
+ `/issue/${args.parentKey}?fields=customfield_10001`, {}, inst,
3118
+ );
3119
+ if (parentIssue.fields?.customfield_10001) {
3120
+ fields.customfield_10001 = parentIssue.fields.customfield_10001;
3121
+ } else if (inst.defaultTeam) {
3122
+ teamWarning = `\n\nNote: Parent ${args.parentKey} has no team assigned. Instance default team is "${inst.defaultTeam.name}". Use team parameter to assign it.`;
3123
+ }
3124
+ }
2970
3125
 
2971
3126
  const result = await fetchJira("/issue", { method: "POST", body: { fields } }, inst);
2972
3127
  const newKey = result.key;
@@ -2974,7 +3129,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2974
3129
  content: [
2975
3130
  {
2976
3131
  type: "text",
2977
- text: `Created subtask ${newKey} under ${args.parentKey}: ${args.summary}\nURL: ${inst.baseUrl}/browse/${newKey}`,
3132
+ text: `Created subtask ${newKey} under ${args.parentKey}: ${args.summary}\nURL: ${inst.baseUrl}/browse/${newKey}${teamWarning}`,
2978
3133
  },
2979
3134
  ],
2980
3135
  };
@@ -3286,7 +3441,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3286
3441
  ? getInstanceByName(args.instance)
3287
3442
  : getInstanceForKey(args.issueKey);
3288
3443
  const original = await fetchJira(
3289
- `/issue/${args.issueKey}?fields=summary,description,priority,labels,components,issuetype,project`,
3444
+ `/issue/${args.issueKey}?fields=summary,description,priority,labels,components,issuetype,project,customfield_10001`,
3290
3445
  {},
3291
3446
  inst,
3292
3447
  );
@@ -3311,6 +3466,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3311
3466
  if (of.components?.length > 0) {
3312
3467
  fields.components = of.components.map((c) => ({ name: c.name }));
3313
3468
  }
3469
+ let teamPrompt = "";
3470
+ if (of.customfield_10001) {
3471
+ fields.customfield_10001 = of.customfield_10001;
3472
+ } else if (inst.defaultTeam) {
3473
+ fields.customfield_10001 = inst.defaultTeam.id;
3474
+ } else {
3475
+ try {
3476
+ const teams = await fetchJiraTeams(
3477
+ `/teams/find?query=&excludeMembers=true`, {}, inst,
3478
+ );
3479
+ if (teams && teams.length > 0) {
3480
+ const list = teams.map((t, i) => `${i + 1}. ${t.title}`).join("\n");
3481
+ teamPrompt = `\n\n⚠ No team assigned (original had none) and no default team configured for instance "${inst.name}". Available teams:\n${list}\n0. None\n\nTo assign a team to this ticket, call jira_update_ticket with issueKey and team parameter.\nIf a team is selected, ask the user if it should also be saved as the default team for instance "${inst.name}" (via jira_add_instance with defaultTeam).`;
3482
+ }
3483
+ } catch {
3484
+ // Teams API not available, proceed without team
3485
+ }
3486
+ }
3314
3487
 
3315
3488
  const result = await fetchJira("/issue", { method: "POST", body: { fields } }, inst);
3316
3489
  const newKey = result.key;
@@ -3347,7 +3520,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
3347
3520
  content: [
3348
3521
  {
3349
3522
  type: "text",
3350
- text: `Cloned ${args.issueKey} -> ${newKey}: ${prefix}${of.summary}\nURL: ${inst.baseUrl}/browse/${newKey}`,
3523
+ text: `Cloned ${args.issueKey} -> ${newKey}: ${prefix}${of.summary}\nURL: ${inst.baseUrl}/browse/${newKey}${teamPrompt}`,
3351
3524
  },
3352
3525
  ],
3353
3526
  };
@@ -3521,5 +3694,5 @@ if (require.main === module) {
3521
3694
 
3522
3695
  // Export for testing
3523
3696
  if (typeof module !== "undefined") {
3524
- module.exports = { buildCommentADF, parseInlineFormatting, findJiraTicketKeys };
3697
+ module.exports = { buildCommentADF, parseInlineFormatting, findJiraTicketKeys, resolveTeamId, fetchJiraTeams };
3525
3698
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rui.branco/jira-mcp",
3
- "version": "1.6.14",
3
+ "version": "1.6.16",
4
4
  "description": "Jira MCP server for Claude Code - fetch tickets, search with JQL, update tickets, manage comments, change status, and get Figma designs",
5
5
  "main": "index.js",
6
6
  "bin": {
package/setup.js CHANGED
@@ -126,17 +126,26 @@ async function setup() {
126
126
  console.log(`Existing single-instance config found (${existing.baseUrl})\n`);
127
127
  } else {
128
128
  console.log("Existing instances:");
129
+ const missingTeam = [];
129
130
  for (const inst of existing.instances) {
130
131
  const isDefault = inst.name === existing.defaultInstance ? " (default)" : "";
131
132
  const projs = inst.projects?.length > 0 ? ` [${inst.projects.join(", ")}]` : "";
132
- console.log(` - ${inst.name}${isDefault}: ${inst.baseUrl}${projs}`);
133
+ const team = inst.defaultTeam ? ` | Team: ${inst.defaultTeam.name}` : "";
134
+ console.log(` - ${inst.name}${isDefault}: ${inst.baseUrl}${projs}${team}`);
135
+ if (!inst.defaultTeam) missingTeam.push(inst.name);
136
+ }
137
+ if (missingTeam.length > 0) {
138
+ console.log(`\n ⚠ No default team: ${missingTeam.join(", ")}`);
133
139
  }
134
140
  console.log();
135
141
  }
136
142
 
137
- const action = await ask("Add new instance, or fresh setup? (add/fresh): ");
138
- if (action.trim().toLowerCase() === "fresh") {
143
+ const action = await ask("Add new instance, set default team, or fresh setup? (add/team/fresh): ");
144
+ const choice = action.trim().toLowerCase();
145
+ if (choice === "fresh") {
139
146
  // Fall through to single setup
147
+ } else if (choice === "team") {
148
+ return await setInstanceTeam(existing);
140
149
  } else {
141
150
  // Add instance to multi-instance config
142
151
  return await addInstance(existing);
@@ -152,7 +161,12 @@ async function setup() {
152
161
  const token = await ask("Jira API token: ");
153
162
  const baseUrl = await ask("Jira base URL (e.g., https://company.atlassian.net): ");
154
163
 
155
- saveConfig({ email, token, baseUrl: baseUrl.replace(/\/$/, "") });
164
+ const config = { email, token, baseUrl: baseUrl.replace(/\/$/, "") };
165
+ const authStr = Buffer.from(`${email}:${token}`).toString("base64");
166
+ const team = await pickDefaultTeam(baseUrl.replace(/\/$/, ""), authStr);
167
+ if (team) config.defaultTeam = team;
168
+
169
+ saveConfig(config);
156
170
  console.log(`\nConfig saved to ${configPath}`);
157
171
 
158
172
  printFigmaStatus();
@@ -160,6 +174,85 @@ async function setup() {
160
174
  rl.close();
161
175
  }
162
176
 
177
+ async function pickDefaultTeam(baseUrl, authStr) {
178
+ console.log("\nFetching available teams from Jira...");
179
+ try {
180
+ const response = await fetch(`${baseUrl}/rest/teams/1.0/teams/find?query=&excludeMembers=true`, {
181
+ headers: {
182
+ Authorization: `Basic ${authStr}`,
183
+ Accept: "application/json",
184
+ },
185
+ });
186
+ if (!response.ok) {
187
+ console.log("Could not fetch teams (API returned " + response.status + "). Skipping team setup.");
188
+ return null;
189
+ }
190
+ const teams = JSON.parse(await response.text());
191
+ if (!teams || teams.length === 0) {
192
+ console.log("No teams found. Skipping team setup.");
193
+ return null;
194
+ }
195
+
196
+ console.log("\nAvailable teams:");
197
+ for (let i = 0; i < teams.length; i++) {
198
+ console.log(` ${i + 1}. ${teams[i].title}`);
199
+ }
200
+ const choice = await ask("\nSelect default team number (or press Enter to skip): ");
201
+ const idx = parseInt(choice, 10) - 1;
202
+ if (isNaN(idx) || idx < 0 || idx >= teams.length) {
203
+ console.log("No default team set.");
204
+ return null;
205
+ }
206
+ const selected = teams[idx];
207
+ const teamObj = { name: selected.title, id: `${selected.organizationId}-${selected.id}` };
208
+ console.log(`Default team set to: ${teamObj.name}`);
209
+ return teamObj;
210
+ } catch (e) {
211
+ console.log("Could not fetch teams: " + e.message + ". Skipping team setup.");
212
+ return null;
213
+ }
214
+ }
215
+
216
+ async function setInstanceTeam(config) {
217
+ if (!config.instances || config.instances.length === 0) {
218
+ console.log("No instances configured.");
219
+ rl.close();
220
+ return;
221
+ }
222
+
223
+ let target;
224
+ if (config.instances.length === 1) {
225
+ target = config.instances[0];
226
+ console.log(`\nConfiguring team for "${target.name}"...`);
227
+ } else {
228
+ console.log("\nWhich instance?");
229
+ for (let i = 0; i < config.instances.length; i++) {
230
+ const inst = config.instances[i];
231
+ const team = inst.defaultTeam ? ` (current: ${inst.defaultTeam.name})` : " (no team)";
232
+ console.log(` ${i + 1}. ${inst.name}${team}`);
233
+ }
234
+ const choice = await ask("Select instance number: ");
235
+ const idx = parseInt(choice, 10) - 1;
236
+ if (isNaN(idx) || idx < 0 || idx >= config.instances.length) {
237
+ console.log("Invalid selection.");
238
+ rl.close();
239
+ return;
240
+ }
241
+ target = config.instances[idx];
242
+ }
243
+
244
+ const authStr = Buffer.from(`${target.email}:${target.token}`).toString("base64");
245
+ const team = await pickDefaultTeam(target.baseUrl, authStr);
246
+ if (team) {
247
+ target.defaultTeam = team;
248
+ saveConfig(config);
249
+ console.log(`\nSaved default team "${team.name}" for instance "${target.name}".`);
250
+ } else {
251
+ console.log("No changes made.");
252
+ }
253
+ rl.close();
254
+ }
255
+
163
256
  async function addInstance(config) {
164
257
  // Migrate old format if needed
165
258
  if (config.email && !config.instances) {
@@ -192,13 +285,18 @@ async function addInstance(config) {
192
285
  const projectsInput = await ask("Project prefixes (comma-separated, e.g., SIDE,FUN): ");
193
286
  const projects = projectsInput.split(",").map((p) => p.trim().toUpperCase()).filter(Boolean);
194
287
 
288
+ const trimmedBaseUrl = baseUrl.trim().replace(/\/$/, "");
289
+ const authStr = Buffer.from(`${email.trim()}:${token.trim()}`).toString("base64");
290
+ const team = await pickDefaultTeam(trimmedBaseUrl, authStr);
291
+
195
292
  const instance = {
196
293
  name: name.trim(),
197
294
  email: email.trim(),
198
295
  token: token.trim(),
199
- baseUrl: baseUrl.trim().replace(/\/$/, ""),
296
+ baseUrl: trimmedBaseUrl,
200
297
  projects,
201
298
  };
299
+ if (team) instance.defaultTeam = team;
202
300
 
203
301
  // Replace or add
204
302
  const idx = config.instances.findIndex((i) => i.name === instance.name);