@bragduck/cli 2.28.1 → 2.30.2

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.
@@ -9,11 +9,11 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
- // node_modules/tsup/assets/esm_shims.js
12
+ // ../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/esm_shims.js
13
13
  import path from "path";
14
14
  import { fileURLToPath } from "url";
15
15
  var init_esm_shims = __esm({
16
- "node_modules/tsup/assets/esm_shims.js"() {
16
+ "../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/esm_shims.js"() {
17
17
  "use strict";
18
18
  }
19
19
  });
@@ -22,7 +22,7 @@ var init_esm_shims = __esm({
22
22
  import { config } from "dotenv";
23
23
  import { fileURLToPath as fileURLToPath2 } from "url";
24
24
  import { dirname, join } from "path";
25
- var __filename, __dirname, APP_NAME, CONFIG_KEYS, DEFAULT_CONFIG, OAUTH_CONFIG, API_ENDPOINTS, ENCRYPTION_CONFIG, STORAGE_PATHS, HTTP_STATUS, CONFIG_FILES;
25
+ var __filename, __dirname, APP_NAME, CONFIG_KEYS, DEFAULT_CONFIG, OAUTH_CONFIG, ATLASSIAN_OAUTH_CONFIG, API_ENDPOINTS, ENCRYPTION_CONFIG, STORAGE_PATHS, HTTP_STATUS, CONFIG_FILES;
26
26
  var init_constants = __esm({
27
27
  "src/constants.ts"() {
28
28
  "use strict";
@@ -55,13 +55,25 @@ var init_constants = __esm({
55
55
  TIMEOUT_MS: 12e4,
56
56
  // 2 minutes
57
57
  MIN_PORT: 8e3,
58
- MAX_PORT: 9e3
58
+ MAX_PORT: 8004
59
+ };
60
+ ATLASSIAN_OAUTH_CONFIG = {
61
+ CLIENT_ID: "kJlsd66DLTnENE8p7Ru2JwqZg1Sie4yZ",
62
+ AUTH_URL: "https://auth.atlassian.com/authorize",
63
+ AUDIENCE: "api.atlassian.com",
64
+ SCOPES: "read:jira-work read:jira-user read:page:confluence read:confluence-user offline_access read:me",
65
+ ACCESSIBLE_RESOURCES_URL: "https://api.atlassian.com/oauth/token/accessible-resources",
66
+ API_GATEWAY_URL: "https://api.atlassian.com"
59
67
  };
60
68
  API_ENDPOINTS = {
61
69
  AUTH: {
62
70
  INITIATE: "/v1/auth/cli/initiate",
63
71
  TOKEN: "/v1/auth/cli/token"
64
72
  },
73
+ ATLASSIAN: {
74
+ TOKEN: "/v1/auth/atlassian/token",
75
+ REFRESH: "/v1/auth/atlassian/refresh"
76
+ },
65
77
  BRAGS: {
66
78
  CREATE: "/v1/brags",
67
79
  LIST: "/v1/brags",
@@ -157,7 +169,7 @@ var init_env_loader = __esm({
157
169
  });
158
170
 
159
171
  // src/utils/errors.ts
160
- var BragduckError, AuthenticationError, GitError, ApiError, NetworkError, ValidationError, OAuthError, TokenExpiredError, GitHubError, BitbucketError, GitLabError, JiraError, ConfluenceError;
172
+ var BragduckError, AuthenticationError, GitError, ApiError, NetworkError, ValidationError, OAuthError, TokenExpiredError, GitHubError, BitbucketError, GitLabError, JiraError, ConfluenceError, AtlassianError;
161
173
  var init_errors = __esm({
162
174
  "src/utils/errors.ts"() {
163
175
  "use strict";
@@ -248,6 +260,12 @@ var init_errors = __esm({
248
260
  this.name = "ConfluenceError";
249
261
  }
250
262
  };
263
+ AtlassianError = class extends BragduckError {
264
+ constructor(message, details) {
265
+ super(message, "ATLASSIAN_ERROR", details);
266
+ this.name = "AtlassianError";
267
+ }
268
+ };
251
269
  }
252
270
  });
253
271
 
@@ -770,92 +788,95 @@ async function findAvailablePort() {
770
788
  async function startOAuthCallbackServer(expectedState) {
771
789
  const port = await findAvailablePort();
772
790
  const timeout = OAUTH_CONFIG.TIMEOUT_MS;
773
- return new Promise((resolve, reject) => {
791
+ const callbackUrl = `http://127.0.0.1:${port}${OAUTH_CONFIG.CALLBACK_PATH}`;
792
+ return new Promise((resolveHandle, rejectHandle) => {
774
793
  let server = null;
775
794
  let timeoutId = null;
776
- const cleanup = () => {
777
- if (timeoutId) {
778
- globalThis.clearTimeout(timeoutId);
779
- }
780
- if (server) {
781
- if (typeof server.closeAllConnections === "function") {
782
- server.closeAllConnections();
783
- }
784
- server.close(() => {
785
- logger.debug("OAuth server closed");
786
- });
787
- server.unref();
788
- }
789
- };
790
- const handleRequest = (req, res) => {
791
- const parsedUrl = parse(req.url || "", true);
792
- logger.debug(`OAuth callback received: ${req.url}`);
793
- if (parsedUrl.pathname === OAUTH_CONFIG.CALLBACK_PATH) {
794
- const { code, state, error, error_description } = parsedUrl.query;
795
- if (error) {
796
- const errorMsg = error_description || error;
797
- logger.debug(`OAuth error: ${errorMsg}`);
798
- res.writeHead(400, { "Content-Type": "text/html" });
799
- res.end(ERROR_HTML(String(errorMsg)));
800
- cleanup();
801
- reject(new OAuthError(`OAuth error: ${errorMsg}`));
802
- return;
795
+ const resultPromise = new Promise((resolveResult, rejectResult) => {
796
+ const cleanup = () => {
797
+ if (timeoutId) {
798
+ globalThis.clearTimeout(timeoutId);
803
799
  }
804
- if (!code || !state) {
805
- const errorMsg = "Missing code or state parameter";
806
- logger.debug(errorMsg);
807
- res.writeHead(400, { "Content-Type": "text/html" });
808
- res.end(ERROR_HTML(errorMsg));
809
- cleanup();
810
- reject(new OAuthError(errorMsg));
811
- return;
800
+ if (server) {
801
+ if (typeof server.closeAllConnections === "function") {
802
+ server.closeAllConnections();
803
+ }
804
+ server.close(() => {
805
+ logger.debug("OAuth server closed");
806
+ });
807
+ server.unref();
812
808
  }
813
- if (state !== expectedState) {
814
- const errorMsg = "Invalid state parameter (possible CSRF attack)";
815
- logger.debug(errorMsg);
816
- res.writeHead(400, { "Content-Type": "text/html" });
817
- res.end(ERROR_HTML(errorMsg));
818
- cleanup();
819
- reject(new OAuthError(errorMsg));
809
+ };
810
+ const handleRequest = (req, res) => {
811
+ const parsedUrl = parse(req.url || "", true);
812
+ logger.debug(`OAuth callback received: ${req.url}`);
813
+ if (parsedUrl.pathname === OAUTH_CONFIG.CALLBACK_PATH) {
814
+ const { code, state, error, error_description } = parsedUrl.query;
815
+ if (error) {
816
+ const errorMsg = error_description || error;
817
+ logger.debug(`OAuth error: ${errorMsg}`);
818
+ res.writeHead(400, { "Content-Type": "text/html" });
819
+ res.end(ERROR_HTML(String(errorMsg)));
820
+ cleanup();
821
+ rejectResult(new OAuthError(`OAuth error: ${errorMsg}`));
822
+ return;
823
+ }
824
+ if (!code || !state) {
825
+ const errorMsg = "Missing code or state parameter";
826
+ logger.debug(errorMsg);
827
+ res.writeHead(400, { "Content-Type": "text/html" });
828
+ res.end(ERROR_HTML(errorMsg));
829
+ cleanup();
830
+ rejectResult(new OAuthError(errorMsg));
831
+ return;
832
+ }
833
+ if (state !== expectedState) {
834
+ const errorMsg = "Invalid state parameter (possible CSRF attack)";
835
+ logger.debug(errorMsg);
836
+ res.writeHead(400, { "Content-Type": "text/html" });
837
+ res.end(ERROR_HTML(errorMsg));
838
+ cleanup();
839
+ rejectResult(new OAuthError(errorMsg));
840
+ return;
841
+ }
842
+ res.writeHead(200, { "Content-Type": "text/html" });
843
+ res.end(SUCCESS_HTML);
844
+ globalThis.setTimeout(() => {
845
+ cleanup();
846
+ resolveResult({
847
+ code: String(code),
848
+ state: String(state),
849
+ port
850
+ });
851
+ }, 100);
820
852
  return;
821
853
  }
822
- res.writeHead(200, { "Content-Type": "text/html" });
823
- res.end(SUCCESS_HTML);
824
- globalThis.setTimeout(() => {
825
- cleanup();
826
- resolve({
827
- code: String(code),
828
- state: String(state),
829
- port
830
- });
831
- }, 100);
832
- return;
833
- }
834
- res.writeHead(404, { "Content-Type": "text/plain" });
835
- res.end("Not Found");
836
- };
837
- server = createServer(handleRequest);
838
- server.on("error", (error) => {
839
- logger.debug(`OAuth server error: ${error.message}`);
840
- cleanup();
841
- reject(new OAuthError(`OAuth server error: ${error.message}`));
842
- });
843
- server.listen(port, "127.0.0.1", () => {
844
- logger.debug(
845
- `OAuth callback server listening on http://127.0.0.1:${port}${OAUTH_CONFIG.CALLBACK_PATH}`
846
- );
854
+ res.writeHead(404, { "Content-Type": "text/plain" });
855
+ res.end("Not Found");
856
+ };
857
+ server = createServer(handleRequest);
858
+ server.on("error", (error) => {
859
+ logger.debug(`OAuth server error: ${error.message}`);
860
+ cleanup();
861
+ rejectResult(new OAuthError(`OAuth server error: ${error.message}`));
862
+ rejectHandle(new OAuthError(`OAuth server error: ${error.message}`));
863
+ });
864
+ server.listen(port, "127.0.0.1", () => {
865
+ logger.debug(
866
+ `OAuth callback server listening on http://127.0.0.1:${port}${OAUTH_CONFIG.CALLBACK_PATH}`
867
+ );
868
+ resolveHandle({ callbackUrl, resultPromise });
869
+ });
870
+ timeoutId = globalThis.setTimeout(() => {
871
+ logger.debug("OAuth callback timeout");
872
+ cleanup();
873
+ rejectResult(
874
+ new OAuthError("Authentication timeout - no callback received within 2 minutes")
875
+ );
876
+ }, timeout);
847
877
  });
848
- timeoutId = globalThis.setTimeout(() => {
849
- logger.debug("OAuth callback timeout");
850
- cleanup();
851
- reject(new OAuthError("Authentication timeout - no callback received within 2 minutes"));
852
- }, timeout);
853
878
  });
854
879
  }
855
- async function getCallbackUrl() {
856
- const port = await findAvailablePort();
857
- return `http://127.0.0.1:${port}${OAUTH_CONFIG.CALLBACK_PATH}`;
858
- }
859
880
  var SUCCESS_HTML, ERROR_HTML;
860
881
  var init_oauth_server = __esm({
861
882
  "src/utils/oauth-server.ts"() {
@@ -1118,7 +1139,7 @@ var init_auth_service = __esm({
1118
1139
  async login() {
1119
1140
  logger.debug("Starting OAuth login flow");
1120
1141
  const state = this.generateState();
1121
- const callbackUrl = await getCallbackUrl();
1142
+ const { callbackUrl, resultPromise } = await startOAuthCallbackServer(state);
1122
1143
  storageService.setOAuthState({
1123
1144
  state,
1124
1145
  createdAt: Date.now()
@@ -1127,7 +1148,6 @@ var init_auth_service = __esm({
1127
1148
  logger.debug(`Callback URL: ${callbackUrl}`);
1128
1149
  const authUrl = await this.buildAuthUrl(state, callbackUrl);
1129
1150
  logger.debug(`Authorization URL: ${authUrl}`);
1130
- const serverPromise = startOAuthCallbackServer(state);
1131
1151
  try {
1132
1152
  await openBrowser(authUrl);
1133
1153
  } catch {
@@ -1137,7 +1157,7 @@ var init_auth_service = __esm({
1137
1157
  }
1138
1158
  let callbackResult;
1139
1159
  try {
1140
- callbackResult = await serverPromise;
1160
+ callbackResult = await resultPromise;
1141
1161
  } catch (error) {
1142
1162
  storageService.deleteOAuthState();
1143
1163
  throw error;
@@ -1241,15 +1261,15 @@ __export(version_exports, {
1241
1261
  getCurrentVersion: () => getCurrentVersion,
1242
1262
  version: () => version
1243
1263
  });
1244
- import { readFileSync as readFileSync3 } from "fs";
1245
- import { fileURLToPath as fileURLToPath4 } from "url";
1246
- import { dirname as dirname3, join as join5 } from "path";
1264
+ import { readFileSync as readFileSync4 } from "fs";
1265
+ import { fileURLToPath as fileURLToPath5 } from "url";
1266
+ import { dirname as dirname4, join as join6 } from "path";
1247
1267
  import chalk4 from "chalk";
1248
1268
  import boxen2 from "boxen";
1249
1269
  function getCurrentVersion() {
1250
1270
  try {
1251
- const packageJsonPath2 = join5(__dirname4, "../../package.json");
1252
- const packageJson2 = JSON.parse(readFileSync3(packageJsonPath2, "utf-8"));
1271
+ const packageJsonPath2 = join6(__dirname5, "../../package.json");
1272
+ const packageJson2 = JSON.parse(readFileSync4(packageJsonPath2, "utf-8"));
1253
1273
  return packageJson2.version;
1254
1274
  } catch {
1255
1275
  logger.debug("Failed to read package.json version");
@@ -1331,7 +1351,7 @@ function formatVersion(includePrefix = true) {
1331
1351
  const prefix = includePrefix ? "v" : "";
1332
1352
  return `${prefix}${version}`;
1333
1353
  }
1334
- var __filename4, __dirname4, version;
1354
+ var __filename5, __dirname5, version;
1335
1355
  var init_version = __esm({
1336
1356
  "src/utils/version.ts"() {
1337
1357
  "use strict";
@@ -1339,22 +1359,22 @@ var init_version = __esm({
1339
1359
  init_api_service();
1340
1360
  init_storage_service();
1341
1361
  init_logger();
1342
- __filename4 = fileURLToPath4(import.meta.url);
1343
- __dirname4 = dirname3(__filename4);
1362
+ __filename5 = fileURLToPath5(import.meta.url);
1363
+ __dirname5 = dirname4(__filename5);
1344
1364
  version = getCurrentVersion();
1345
1365
  }
1346
1366
  });
1347
1367
 
1348
1368
  // src/services/api.service.ts
1349
- import { ofetch as ofetch2 } from "ofetch";
1350
- import { readFileSync as readFileSync4 } from "fs";
1351
- import { fileURLToPath as fileURLToPath5 } from "url";
1352
- import { URLSearchParams as URLSearchParams2 } from "url";
1353
- import { dirname as dirname4, join as join6 } from "path";
1369
+ import { ofetch as ofetch3 } from "ofetch";
1370
+ import { readFileSync as readFileSync5 } from "fs";
1371
+ import { fileURLToPath as fileURLToPath6 } from "url";
1372
+ import { URLSearchParams as URLSearchParams3 } from "url";
1373
+ import { dirname as dirname5, join as join7 } from "path";
1354
1374
  function getCliVersion() {
1355
1375
  try {
1356
- const packageJsonPath2 = join6(__dirname5, "../../package.json");
1357
- const packageJson2 = JSON.parse(readFileSync4(packageJsonPath2, "utf-8"));
1376
+ const packageJsonPath2 = join7(__dirname6, "../../package.json");
1377
+ const packageJson2 = JSON.parse(readFileSync5(packageJsonPath2, "utf-8"));
1358
1378
  return packageJson2.version;
1359
1379
  } catch {
1360
1380
  logger.debug("Failed to read package.json version");
@@ -1366,7 +1386,7 @@ function getPlatformInfo() {
1366
1386
  const arch = process.arch;
1367
1387
  return `${platform}-${arch}`;
1368
1388
  }
1369
- var __filename5, __dirname5, ApiService, apiService;
1389
+ var __filename6, __dirname6, ApiService, apiService;
1370
1390
  var init_api_service = __esm({
1371
1391
  "src/services/api.service.ts"() {
1372
1392
  "use strict";
@@ -1375,14 +1395,14 @@ var init_api_service = __esm({
1375
1395
  init_constants();
1376
1396
  init_errors();
1377
1397
  init_logger();
1378
- __filename5 = fileURLToPath5(import.meta.url);
1379
- __dirname5 = dirname4(__filename5);
1398
+ __filename6 = fileURLToPath6(import.meta.url);
1399
+ __dirname6 = dirname5(__filename6);
1380
1400
  ApiService = class {
1381
1401
  baseURL;
1382
1402
  client;
1383
1403
  constructor() {
1384
1404
  this.baseURL = process.env.API_BASE_URL || "https://api.bragduck.com";
1385
- this.client = ofetch2.create({
1405
+ this.client = ofetch3.create({
1386
1406
  baseURL: this.baseURL,
1387
1407
  // Request interceptor
1388
1408
  onRequest: async ({ request, options }) => {
@@ -1546,7 +1566,7 @@ var init_api_service = __esm({
1546
1566
  const { limit = 50, offset = 0, tags, search } = params;
1547
1567
  logger.debug(`Listing brags: limit=${limit}, offset=${offset}`);
1548
1568
  try {
1549
- const queryParams = new URLSearchParams2({
1569
+ const queryParams = new URLSearchParams3({
1550
1570
  limit: limit.toString(),
1551
1571
  offset: offset.toString()
1552
1572
  });
@@ -1643,7 +1663,7 @@ var init_api_service = __esm({
1643
1663
  */
1644
1664
  setBaseURL(url) {
1645
1665
  this.baseURL = url;
1646
- this.client = ofetch2.create({
1666
+ this.client = ofetch3.create({
1647
1667
  baseURL: url
1648
1668
  });
1649
1669
  }
@@ -1661,18 +1681,266 @@ var init_api_service = __esm({
1661
1681
  // src/cli.ts
1662
1682
  init_esm_shims();
1663
1683
  import { Command } from "commander";
1664
- import { readFileSync as readFileSync5 } from "fs";
1665
- import { fileURLToPath as fileURLToPath6 } from "url";
1666
- import { dirname as dirname5, join as join7 } from "path";
1684
+ import { readFileSync as readFileSync6 } from "fs";
1685
+ import { fileURLToPath as fileURLToPath7 } from "url";
1686
+ import { dirname as dirname6, join as join8 } from "path";
1667
1687
 
1668
1688
  // src/commands/auth.ts
1669
1689
  init_esm_shims();
1670
1690
  init_auth_service();
1671
- init_storage_service();
1672
1691
  import boxen from "boxen";
1673
1692
  import chalk3 from "chalk";
1674
1693
  import { input } from "@inquirer/prompts";
1675
1694
 
1695
+ // src/services/atlassian-auth.service.ts
1696
+ init_esm_shims();
1697
+ init_constants();
1698
+ init_storage_service();
1699
+ init_oauth_server();
1700
+ init_browser();
1701
+ init_errors();
1702
+ init_logger();
1703
+ import { randomBytes as randomBytes3 } from "crypto";
1704
+ import { readFileSync as readFileSync3 } from "fs";
1705
+ import { fileURLToPath as fileURLToPath4, URLSearchParams as URLSearchParams2 } from "url";
1706
+ import { dirname as dirname3, join as join4 } from "path";
1707
+ import { ofetch as ofetch2 } from "ofetch";
1708
+ import { select } from "@inquirer/prompts";
1709
+ var __filename4 = fileURLToPath4(import.meta.url);
1710
+ var __dirname4 = dirname3(__filename4);
1711
+ function getUserAgent2() {
1712
+ try {
1713
+ const packageJsonPath2 = join4(__dirname4, "../../package.json");
1714
+ const packageJson2 = JSON.parse(readFileSync3(packageJsonPath2, "utf-8"));
1715
+ const version2 = packageJson2.version;
1716
+ const platform = process.platform;
1717
+ const arch = process.arch;
1718
+ return `BragDuck-CLI/${version2} (${platform}-${arch})`;
1719
+ } catch {
1720
+ logger.debug("Failed to read package.json version");
1721
+ return "BragDuck-CLI/2.0.0";
1722
+ }
1723
+ }
1724
+ var AtlassianAuthService = class {
1725
+ apiBaseUrl;
1726
+ refreshPromise = null;
1727
+ constructor() {
1728
+ this.apiBaseUrl = process.env.API_BASE_URL || "https://api.bragduck.com";
1729
+ }
1730
+ /**
1731
+ * Generate a random state string for CSRF protection
1732
+ */
1733
+ generateState() {
1734
+ return randomBytes3(32).toString("hex");
1735
+ }
1736
+ /**
1737
+ * Build the Atlassian OAuth authorization URL
1738
+ */
1739
+ buildAuthUrl(state, callbackUrl) {
1740
+ const params = new URLSearchParams2({
1741
+ audience: ATLASSIAN_OAUTH_CONFIG.AUDIENCE,
1742
+ client_id: ATLASSIAN_OAUTH_CONFIG.CLIENT_ID,
1743
+ scope: ATLASSIAN_OAUTH_CONFIG.SCOPES,
1744
+ redirect_uri: callbackUrl,
1745
+ state,
1746
+ response_type: "code",
1747
+ prompt: "consent"
1748
+ });
1749
+ return `${ATLASSIAN_OAUTH_CONFIG.AUTH_URL}?${params.toString()}`;
1750
+ }
1751
+ /**
1752
+ * Exchange authorization code for tokens via BragDuck backend
1753
+ */
1754
+ async exchangeCodeForToken(code, redirectUri) {
1755
+ try {
1756
+ logger.debug("Exchanging Atlassian authorization code for token via backend");
1757
+ const response = await ofetch2(
1758
+ `${this.apiBaseUrl}${API_ENDPOINTS.ATLASSIAN.TOKEN}`,
1759
+ {
1760
+ method: "POST",
1761
+ body: {
1762
+ code,
1763
+ redirect_uri: redirectUri
1764
+ },
1765
+ headers: {
1766
+ "Content-Type": "application/json",
1767
+ "User-Agent": getUserAgent2()
1768
+ }
1769
+ }
1770
+ );
1771
+ logger.debug("Atlassian token exchange successful");
1772
+ return response;
1773
+ } catch (error) {
1774
+ logger.debug(`Atlassian token exchange failed: ${error.message}`);
1775
+ throw new AtlassianError("Failed to exchange Atlassian authorization code", {
1776
+ originalError: error.message
1777
+ });
1778
+ }
1779
+ }
1780
+ /**
1781
+ * Fetch accessible Atlassian Cloud resources (sites) using the access token
1782
+ */
1783
+ async fetchAccessibleResources(accessToken) {
1784
+ try {
1785
+ logger.debug("Fetching accessible Atlassian resources");
1786
+ const resources = await ofetch2(
1787
+ ATLASSIAN_OAUTH_CONFIG.ACCESSIBLE_RESOURCES_URL,
1788
+ {
1789
+ headers: {
1790
+ Authorization: `Bearer ${accessToken}`,
1791
+ Accept: "application/json"
1792
+ }
1793
+ }
1794
+ );
1795
+ logger.debug(`Found ${resources.length} accessible resource(s)`);
1796
+ return resources;
1797
+ } catch (error) {
1798
+ logger.debug(`Failed to fetch accessible resources: ${error.message}`);
1799
+ throw new AtlassianError("Failed to fetch Atlassian Cloud sites", {
1800
+ originalError: error.message
1801
+ });
1802
+ }
1803
+ }
1804
+ /**
1805
+ * Let user pick a site if multiple accessible resources exist
1806
+ */
1807
+ async selectSite(resources) {
1808
+ if (resources.length === 0) {
1809
+ throw new AtlassianError(
1810
+ "No accessible Atlassian Cloud sites found. Make sure your account has access to at least one site."
1811
+ );
1812
+ }
1813
+ if (resources.length === 1) {
1814
+ const site = resources[0];
1815
+ logger.debug(`Auto-selecting single site: ${site.name}`);
1816
+ return site;
1817
+ }
1818
+ const selectedId = await select({
1819
+ message: "Select your Atlassian Cloud site:",
1820
+ choices: resources.map((r) => ({
1821
+ name: `${r.name} (${r.url})`,
1822
+ value: r.id
1823
+ }))
1824
+ });
1825
+ return resources.find((r) => r.id === selectedId);
1826
+ }
1827
+ /**
1828
+ * Full OAuth login flow:
1829
+ * 1. Open browser to Atlassian consent screen
1830
+ * 2. Wait for callback with authorization code
1831
+ * 3. Exchange code for tokens via BragDuck backend
1832
+ * 4. Fetch accessible resources and pick site
1833
+ * 5. Store credentials for jira, confluence, and atlassian services
1834
+ */
1835
+ async login() {
1836
+ logger.debug("Starting Atlassian OAuth login flow");
1837
+ const state = this.generateState();
1838
+ const { callbackUrl, resultPromise } = await startOAuthCallbackServer(state);
1839
+ storageService.setOAuthState({
1840
+ state,
1841
+ createdAt: Date.now()
1842
+ });
1843
+ logger.debug(`OAuth state: ${state}`);
1844
+ logger.debug(`Callback URL: ${callbackUrl}`);
1845
+ const authUrl = this.buildAuthUrl(state, callbackUrl);
1846
+ logger.debug(`Authorization URL: ${authUrl}`);
1847
+ try {
1848
+ await openBrowser(authUrl);
1849
+ } catch {
1850
+ logger.warning("Could not open browser automatically");
1851
+ logger.info("Please open this URL in your browser:");
1852
+ logger.log(authUrl);
1853
+ }
1854
+ let callbackResult;
1855
+ try {
1856
+ callbackResult = await resultPromise;
1857
+ } catch (error) {
1858
+ storageService.deleteOAuthState();
1859
+ throw error;
1860
+ }
1861
+ storageService.deleteOAuthState();
1862
+ const tokenResponse = await this.exchangeCodeForToken(callbackResult.code, callbackUrl);
1863
+ const resources = await this.fetchAccessibleResources(tokenResponse.access_token);
1864
+ const site = await this.selectSite(resources);
1865
+ const expiresAt = tokenResponse.expires_in ? Date.now() + tokenResponse.expires_in * 1e3 : void 0;
1866
+ const credentials = {
1867
+ accessToken: tokenResponse.access_token,
1868
+ refreshToken: tokenResponse.refresh_token,
1869
+ expiresAt,
1870
+ instanceUrl: site.url,
1871
+ // Real Cloud URL for browse links
1872
+ cloudId: site.id,
1873
+ authMethod: "oauth"
1874
+ };
1875
+ await storageService.setServiceCredentials("jira", credentials);
1876
+ await storageService.setServiceCredentials("confluence", credentials);
1877
+ await storageService.setServiceCredentials("atlassian", credentials);
1878
+ logger.debug("Atlassian OAuth login successful");
1879
+ return { siteName: site.name, siteUrl: site.url };
1880
+ }
1881
+ /**
1882
+ * Refresh Atlassian OAuth tokens via BragDuck backend.
1883
+ * Uses a singleton promise to prevent concurrent refresh race conditions
1884
+ * (Atlassian uses rotating refresh tokens - each refresh invalidates the previous one).
1885
+ */
1886
+ async refreshToken() {
1887
+ if (this.refreshPromise) {
1888
+ return this.refreshPromise;
1889
+ }
1890
+ this.refreshPromise = this.doRefreshToken();
1891
+ try {
1892
+ await this.refreshPromise;
1893
+ } finally {
1894
+ this.refreshPromise = null;
1895
+ }
1896
+ }
1897
+ async doRefreshToken() {
1898
+ logger.debug("Refreshing Atlassian OAuth token");
1899
+ const creds = await storageService.getServiceCredentials("jira");
1900
+ if (!creds?.refreshToken) {
1901
+ throw new AtlassianError("No Atlassian refresh token available", {
1902
+ hint: "Run: bragduck auth atlassian"
1903
+ });
1904
+ }
1905
+ try {
1906
+ const response = await ofetch2(
1907
+ `${this.apiBaseUrl}${API_ENDPOINTS.ATLASSIAN.REFRESH}`,
1908
+ {
1909
+ method: "POST",
1910
+ body: {
1911
+ refresh_token: creds.refreshToken
1912
+ },
1913
+ headers: {
1914
+ "Content-Type": "application/json",
1915
+ "User-Agent": getUserAgent2()
1916
+ }
1917
+ }
1918
+ );
1919
+ const expiresAt = response.expires_in ? Date.now() + response.expires_in * 1e3 : void 0;
1920
+ const updatedCreds = {
1921
+ ...creds,
1922
+ accessToken: response.access_token,
1923
+ refreshToken: response.refresh_token,
1924
+ expiresAt
1925
+ };
1926
+ await storageService.setServiceCredentials("jira", updatedCreds);
1927
+ await storageService.setServiceCredentials("confluence", updatedCreds);
1928
+ await storageService.setServiceCredentials("atlassian", updatedCreds);
1929
+ logger.debug("Atlassian token refresh successful");
1930
+ } catch (error) {
1931
+ logger.debug(`Atlassian token refresh failed: ${error.message}`);
1932
+ throw new AtlassianError(
1933
+ 'Atlassian token refresh failed. Please run "bragduck auth atlassian" to re-authenticate.',
1934
+ { originalError: error.message }
1935
+ );
1936
+ }
1937
+ }
1938
+ };
1939
+ var atlassianAuthService = new AtlassianAuthService();
1940
+
1941
+ // src/commands/auth.ts
1942
+ init_storage_service();
1943
+
1676
1944
  // src/services/github.service.ts
1677
1945
  init_esm_shims();
1678
1946
  init_errors();
@@ -1688,9 +1956,9 @@ import simpleGit from "simple-git";
1688
1956
  init_esm_shims();
1689
1957
  init_errors();
1690
1958
  import { existsSync as existsSync2 } from "fs";
1691
- import { join as join4 } from "path";
1959
+ import { join as join5 } from "path";
1692
1960
  function validateGitRepository(path4) {
1693
- const gitDir = join4(path4, ".git");
1961
+ const gitDir = join5(path4, ".git");
1694
1962
  if (!existsSync2(gitDir)) {
1695
1963
  throw new GitError(
1696
1964
  "Not a git repository. Please run this command from within a git repository.",
@@ -2404,7 +2672,14 @@ async function authStatus() {
2404
2672
  }
2405
2673
  for (const service of services) {
2406
2674
  if (service !== "bragduck") {
2407
- logger.info(`${colors.success("\u2713")} ${service}: Authenticated`);
2675
+ const creds = await storageService.getServiceCredentials(service);
2676
+ let methodLabel = "";
2677
+ if (creds?.authMethod === "oauth") {
2678
+ methodLabel = " (Cloud - OAuth)";
2679
+ } else if (creds?.authMethod === "basic") {
2680
+ methodLabel = " (Server - API Token)";
2681
+ }
2682
+ logger.info(`${colors.success("\u2713")} ${service}: Authenticated${methodLabel}`);
2408
2683
  }
2409
2684
  }
2410
2685
  logger.log("");
@@ -2556,68 +2831,21 @@ User: ${user.name} (@${user.username})`,
2556
2831
  }
2557
2832
  async function authAtlassian() {
2558
2833
  logger.log("");
2559
- logger.log(
2560
- boxen(
2561
- theme.info("Atlassian API Token Authentication") + "\n\nThis token works for Jira, Confluence, and Bitbucket\n\nCreate an API Token at:\n" + colors.highlight("https://id.atlassian.com/manage-profile/security/api-tokens") + "\n\nRequired access:\n \u2022 Jira: Read issues\n \u2022 Confluence: Read pages\n \u2022 Bitbucket: Read repositories",
2562
- boxStyles.info
2563
- )
2564
- );
2834
+ logger.info("Opening browser for Atlassian Cloud authentication...");
2565
2835
  logger.log("");
2566
2836
  try {
2567
- const instanceUrl = await input({
2568
- message: "Atlassian instance URL (e.g., company.atlassian.net):",
2569
- validate: (value) => value.length > 0 ? true : "Instance URL cannot be empty"
2570
- });
2571
- const email = await input({
2572
- message: "Atlassian account email:",
2573
- validate: (value) => value.includes("@") ? true : "Please enter a valid email address"
2574
- });
2575
- const apiToken = await input({
2576
- message: "API Token:",
2577
- validate: (value) => value.length > 0 ? true : "API token cannot be empty"
2578
- });
2579
- const testInstanceUrl = instanceUrl.startsWith("http") ? instanceUrl : `https://${instanceUrl}`;
2580
- const auth = Buffer.from(`${email}:${apiToken}`).toString("base64");
2581
- const response = await fetch(`${testInstanceUrl}/rest/api/2/myself`, {
2582
- headers: { Authorization: `Basic ${auth}` }
2583
- });
2584
- if (!response.ok) {
2585
- logger.log("");
2586
- logger.log(
2587
- boxen(
2588
- theme.error("\u2717 Authentication Failed") + "\n\nInvalid instance URL, email, or API token\nMake sure the instance URL is correct (e.g., company.atlassian.net)",
2589
- boxStyles.error
2590
- )
2591
- );
2592
- logger.log("");
2593
- process.exit(1);
2594
- }
2595
- const user = await response.json();
2596
- const credentials = {
2597
- accessToken: apiToken,
2598
- username: email,
2599
- instanceUrl: instanceUrl.startsWith("http") ? instanceUrl : `https://${instanceUrl}`
2600
- };
2601
- await storageService.setServiceCredentials("jira", credentials);
2602
- await storageService.setServiceCredentials("confluence", credentials);
2603
- await storageService.setServiceCredentials("bitbucket", {
2604
- ...credentials,
2605
- instanceUrl: "https://api.bitbucket.org"
2606
- // Bitbucket uses different API base
2607
- });
2837
+ const result = await atlassianAuthService.login();
2608
2838
  logger.log("");
2609
2839
  logger.log(
2610
2840
  boxen(
2611
- theme.success("\u2713 Successfully authenticated with Atlassian") + `
2841
+ theme.success("\u2713 Successfully authenticated with Atlassian Cloud") + `
2612
2842
 
2613
- Instance: ${instanceUrl}
2614
- User: ${user.displayName}
2615
- Email: ${user.emailAddress}
2843
+ Site: ${result.siteName}
2844
+ URL: ${result.siteUrl}
2616
2845
 
2617
2846
  Services configured:
2618
- \u2022 Jira
2619
- \u2022 Confluence
2620
- \u2022 Bitbucket`,
2847
+ - Jira (OAuth)
2848
+ - Confluence (OAuth)`,
2621
2849
  boxStyles.success
2622
2850
  )
2623
2851
  );
@@ -2639,7 +2867,7 @@ Services configured:
2639
2867
  // src/commands/sync.ts
2640
2868
  init_esm_shims();
2641
2869
 
2642
- // node_modules/@inquirer/core/dist/esm/lib/errors.mjs
2870
+ // ../../node_modules/.pnpm/@inquirer+core@9.2.1/node_modules/@inquirer/core/dist/esm/lib/errors.mjs
2643
2871
  init_esm_shims();
2644
2872
  var CancelPromptError = class extends Error {
2645
2873
  name = "CancelPromptError";
@@ -2650,8 +2878,8 @@ var CancelPromptError = class extends Error {
2650
2878
  init_api_service();
2651
2879
  init_storage_service();
2652
2880
  init_auth_service();
2653
- import { select as select3 } from "@inquirer/prompts";
2654
- import boxen6 from "boxen";
2881
+ import { select as select4 } from "@inquirer/prompts";
2882
+ import boxen5 from "boxen";
2655
2883
 
2656
2884
  // src/utils/source-detector.ts
2657
2885
  init_esm_shims();
@@ -2659,7 +2887,7 @@ init_errors();
2659
2887
  init_storage_service();
2660
2888
  import { exec as exec3 } from "child_process";
2661
2889
  import { promisify as promisify3 } from "util";
2662
- import { select } from "@inquirer/prompts";
2890
+ import { select as select2 } from "@inquirer/prompts";
2663
2891
  var execAsync3 = promisify3(exec3);
2664
2892
  var SourceDetector = class {
2665
2893
  /**
@@ -2715,7 +2943,7 @@ var SourceDetector = class {
2715
2943
  description: source.remoteUrl
2716
2944
  };
2717
2945
  });
2718
- return await select({
2946
+ return await select2({
2719
2947
  message: "Multiple sources detected. Which do you want to sync?",
2720
2948
  choices
2721
2949
  });
@@ -3231,7 +3459,7 @@ init_logger();
3231
3459
  init_storage_service();
3232
3460
  import { exec as exec5 } from "child_process";
3233
3461
  import { promisify as promisify5 } from "util";
3234
- import { URLSearchParams as URLSearchParams3 } from "url";
3462
+ import { URLSearchParams as URLSearchParams4 } from "url";
3235
3463
  var execAsync5 = promisify5(exec5);
3236
3464
  var GitLabService = class {
3237
3465
  DEFAULT_INSTANCE = "https://gitlab.com";
@@ -3377,7 +3605,7 @@ var GitLabService = class {
3377
3605
  async getMergedMRs(options = {}) {
3378
3606
  const { projectPath } = await this.getProjectFromGit();
3379
3607
  const encodedPath = encodeURIComponent(projectPath);
3380
- const params = new URLSearchParams3({
3608
+ const params = new URLSearchParams4({
3381
3609
  state: "merged",
3382
3610
  order_by: "updated_at",
3383
3611
  sort: "desc",
@@ -3543,41 +3771,76 @@ init_esm_shims();
3543
3771
  init_errors();
3544
3772
  init_logger();
3545
3773
  init_storage_service();
3774
+ init_constants();
3546
3775
  var JiraService = class {
3547
3776
  MAX_DESCRIPTION_LENGTH = 5e3;
3548
3777
  /**
3549
- * Get stored Jira credentials
3778
+ * Get stored Jira credentials, auto-refreshing OAuth tokens if expired
3550
3779
  */
3551
3780
  async getCredentials() {
3552
3781
  const creds = await storageService.getServiceCredentials("jira");
3553
- if (!creds || !creds.username || !creds.accessToken || !creds.instanceUrl) {
3782
+ if (!creds || !creds.accessToken) {
3554
3783
  throw new JiraError("Not authenticated with Jira", {
3555
3784
  hint: "Run: bragduck auth atlassian"
3556
3785
  });
3557
3786
  }
3558
- if (creds.expiresAt && creds.expiresAt < Date.now()) {
3787
+ if (creds.authMethod !== "oauth" && (!creds.username || !creds.instanceUrl)) {
3788
+ throw new JiraError("Not authenticated with Jira", {
3789
+ hint: "Run: bragduck auth atlassian"
3790
+ });
3791
+ }
3792
+ if (creds.authMethod === "oauth" && !creds.cloudId) {
3793
+ throw new JiraError("Missing Atlassian Cloud ID", {
3794
+ hint: "Run: bragduck auth atlassian"
3795
+ });
3796
+ }
3797
+ if (creds.authMethod === "oauth" && creds.expiresAt && creds.expiresAt < Date.now()) {
3798
+ if (creds.refreshToken) {
3799
+ logger.debug("Jira OAuth token expired, refreshing...");
3800
+ await atlassianAuthService.refreshToken();
3801
+ const refreshed = await storageService.getServiceCredentials("jira");
3802
+ if (!refreshed?.accessToken) {
3803
+ throw new JiraError("Token refresh failed", {
3804
+ hint: "Run: bragduck auth atlassian"
3805
+ });
3806
+ }
3807
+ return refreshed;
3808
+ }
3809
+ throw new JiraError("OAuth token has expired and no refresh token available", {
3810
+ hint: "Run: bragduck auth atlassian"
3811
+ });
3812
+ }
3813
+ if (creds.authMethod !== "oauth" && creds.expiresAt && creds.expiresAt < Date.now()) {
3559
3814
  throw new JiraError("API token has expired", {
3560
3815
  hint: "Run: bragduck auth atlassian"
3561
3816
  });
3562
3817
  }
3563
- return {
3564
- email: creds.username,
3565
- apiToken: creds.accessToken,
3566
- instanceUrl: creds.instanceUrl
3567
- };
3818
+ return creds;
3568
3819
  }
3569
3820
  /**
3570
- * Make authenticated request to Jira API
3821
+ * Make authenticated request to Jira API.
3822
+ * Uses OAuth Bearer + API gateway for Cloud, Basic Auth for Server/DC.
3823
+ * Retries once on 401 for OAuth (auto-refresh).
3571
3824
  */
3572
3825
  async request(endpoint, method = "GET", body) {
3573
- const { email, apiToken, instanceUrl } = await this.getCredentials();
3574
- const auth = Buffer.from(`${email}:${apiToken}`).toString("base64");
3575
- const baseUrl = instanceUrl.startsWith("http") ? instanceUrl : `https://${instanceUrl}`;
3826
+ const creds = await this.getCredentials();
3827
+ const isOAuth = creds.authMethod === "oauth";
3828
+ let baseUrl;
3829
+ let authHeader;
3830
+ if (isOAuth) {
3831
+ baseUrl = `${ATLASSIAN_OAUTH_CONFIG.API_GATEWAY_URL}/ex/jira/${creds.cloudId}`;
3832
+ authHeader = `Bearer ${creds.accessToken}`;
3833
+ } else {
3834
+ const instanceUrl = creds.instanceUrl;
3835
+ baseUrl = instanceUrl.startsWith("http") ? instanceUrl : `https://${instanceUrl}`;
3836
+ const auth = Buffer.from(`${creds.username}:${creds.accessToken}`).toString("base64");
3837
+ authHeader = `Basic ${auth}`;
3838
+ }
3576
3839
  logger.debug(`Jira API: ${method} ${endpoint}`);
3577
3840
  const options = {
3578
3841
  method,
3579
3842
  headers: {
3580
- Authorization: `Basic ${auth}`,
3843
+ Authorization: authHeader,
3581
3844
  "Content-Type": "application/json",
3582
3845
  Accept: "application/json"
3583
3846
  }
@@ -3585,7 +3848,17 @@ var JiraService = class {
3585
3848
  if (body) {
3586
3849
  options.body = JSON.stringify(body);
3587
3850
  }
3588
- const response = await fetch(`${baseUrl}${endpoint}`, options);
3851
+ let response = await fetch(`${baseUrl}${endpoint}`, options);
3852
+ if (response.status === 401 && isOAuth && creds.refreshToken) {
3853
+ logger.debug("Jira OAuth 401, attempting token refresh and retry");
3854
+ await atlassianAuthService.refreshToken();
3855
+ const refreshedCreds = await storageService.getServiceCredentials("jira");
3856
+ if (refreshedCreds?.accessToken) {
3857
+ options.headers.Authorization = `Bearer ${refreshedCreds.accessToken}`;
3858
+ const newBaseUrl = `${ATLASSIAN_OAUTH_CONFIG.API_GATEWAY_URL}/ex/jira/${refreshedCreds.cloudId}`;
3859
+ response = await fetch(`${newBaseUrl}${endpoint}`, options);
3860
+ }
3861
+ }
3589
3862
  if (!response.ok) {
3590
3863
  const statusText = response.statusText;
3591
3864
  const status = response.status;
@@ -3991,40 +4264,75 @@ init_esm_shims();
3991
4264
  init_errors();
3992
4265
  init_logger();
3993
4266
  init_storage_service();
4267
+ init_constants();
3994
4268
  var ConfluenceService = class {
3995
4269
  /**
3996
- * Get stored Confluence credentials
4270
+ * Get stored Confluence credentials, auto-refreshing OAuth tokens if expired
3997
4271
  */
3998
4272
  async getCredentials() {
3999
4273
  const creds = await storageService.getServiceCredentials("confluence");
4000
- if (!creds || !creds.username || !creds.accessToken || !creds.instanceUrl) {
4274
+ if (!creds || !creds.accessToken) {
4001
4275
  throw new ConfluenceError("Not authenticated with Confluence", {
4002
4276
  hint: "Run: bragduck auth atlassian"
4003
4277
  });
4004
4278
  }
4005
- if (creds.expiresAt && creds.expiresAt < Date.now()) {
4279
+ if (creds.authMethod !== "oauth" && (!creds.username || !creds.instanceUrl)) {
4280
+ throw new ConfluenceError("Not authenticated with Confluence", {
4281
+ hint: "Run: bragduck auth atlassian"
4282
+ });
4283
+ }
4284
+ if (creds.authMethod === "oauth" && !creds.cloudId) {
4285
+ throw new ConfluenceError("Missing Atlassian Cloud ID", {
4286
+ hint: "Run: bragduck auth atlassian"
4287
+ });
4288
+ }
4289
+ if (creds.authMethod === "oauth" && creds.expiresAt && creds.expiresAt < Date.now()) {
4290
+ if (creds.refreshToken) {
4291
+ logger.debug("Confluence OAuth token expired, refreshing...");
4292
+ await atlassianAuthService.refreshToken();
4293
+ const refreshed = await storageService.getServiceCredentials("confluence");
4294
+ if (!refreshed?.accessToken) {
4295
+ throw new ConfluenceError("Token refresh failed", {
4296
+ hint: "Run: bragduck auth atlassian"
4297
+ });
4298
+ }
4299
+ return refreshed;
4300
+ }
4301
+ throw new ConfluenceError("OAuth token has expired and no refresh token available", {
4302
+ hint: "Run: bragduck auth atlassian"
4303
+ });
4304
+ }
4305
+ if (creds.authMethod !== "oauth" && creds.expiresAt && creds.expiresAt < Date.now()) {
4006
4306
  throw new ConfluenceError("API token has expired", {
4007
4307
  hint: "Run: bragduck auth atlassian"
4008
4308
  });
4009
4309
  }
4010
- return {
4011
- email: creds.username,
4012
- apiToken: creds.accessToken,
4013
- instanceUrl: creds.instanceUrl
4014
- };
4310
+ return creds;
4015
4311
  }
4016
4312
  /**
4017
- * Make authenticated request to Confluence API
4313
+ * Make authenticated request to Confluence API.
4314
+ * Uses OAuth Bearer + API gateway for Cloud, Basic Auth for Server/DC.
4315
+ * Retries once on 401 for OAuth (auto-refresh).
4018
4316
  */
4019
4317
  async request(endpoint, method = "GET", body) {
4020
- const { email, apiToken, instanceUrl } = await this.getCredentials();
4021
- const auth = Buffer.from(`${email}:${apiToken}`).toString("base64");
4022
- const baseUrl = instanceUrl.startsWith("http") ? instanceUrl : `https://${instanceUrl}`;
4318
+ const creds = await this.getCredentials();
4319
+ const isOAuth = creds.authMethod === "oauth";
4320
+ let baseUrl;
4321
+ let authHeader;
4322
+ if (isOAuth) {
4323
+ baseUrl = `${ATLASSIAN_OAUTH_CONFIG.API_GATEWAY_URL}/ex/confluence/${creds.cloudId}`;
4324
+ authHeader = `Bearer ${creds.accessToken}`;
4325
+ } else {
4326
+ const instanceUrl = creds.instanceUrl;
4327
+ baseUrl = instanceUrl.startsWith("http") ? instanceUrl : `https://${instanceUrl}`;
4328
+ const auth = Buffer.from(`${creds.username}:${creds.accessToken}`).toString("base64");
4329
+ authHeader = `Basic ${auth}`;
4330
+ }
4023
4331
  logger.debug(`Confluence API: ${method} ${endpoint}`);
4024
4332
  const options = {
4025
4333
  method,
4026
4334
  headers: {
4027
- Authorization: `Basic ${auth}`,
4335
+ Authorization: authHeader,
4028
4336
  "Content-Type": "application/json",
4029
4337
  Accept: "application/json"
4030
4338
  }
@@ -4032,7 +4340,17 @@ var ConfluenceService = class {
4032
4340
  if (body) {
4033
4341
  options.body = JSON.stringify(body);
4034
4342
  }
4035
- const response = await fetch(`${baseUrl}${endpoint}`, options);
4343
+ let response = await fetch(`${baseUrl}${endpoint}`, options);
4344
+ if (response.status === 401 && isOAuth && creds.refreshToken) {
4345
+ logger.debug("Confluence OAuth 401, attempting token refresh and retry");
4346
+ await atlassianAuthService.refreshToken();
4347
+ const refreshedCreds = await storageService.getServiceCredentials("confluence");
4348
+ if (refreshedCreds?.accessToken) {
4349
+ options.headers.Authorization = `Bearer ${refreshedCreds.accessToken}`;
4350
+ const newBaseUrl = `${ATLASSIAN_OAUTH_CONFIG.API_GATEWAY_URL}/ex/confluence/${refreshedCreds.cloudId}`;
4351
+ response = await fetch(`${newBaseUrl}${endpoint}`, options);
4352
+ }
4353
+ }
4036
4354
  if (!response.ok) {
4037
4355
  const statusText = response.statusText;
4038
4356
  const status = response.status;
@@ -4441,138 +4759,13 @@ init_logger();
4441
4759
  // src/utils/auth-helper.ts
4442
4760
  init_esm_shims();
4443
4761
  init_auth_service();
4444
- import boxen5 from "boxen";
4762
+ import boxen4 from "boxen";
4763
+ init_logger();
4445
4764
 
4446
- // src/commands/init.ts
4765
+ // src/ui/prompts.ts
4447
4766
  init_esm_shims();
4448
- init_auth_service();
4449
- init_logger();
4450
- import ora from "ora";
4767
+ import { checkbox, confirm, input as input2, select as select3, editor } from "@inquirer/prompts";
4451
4768
  import boxen3 from "boxen";
4452
- import chalk5 from "chalk";
4453
- async function initCommand() {
4454
- logger.log("");
4455
- logger.log(
4456
- boxen3(
4457
- chalk5.yellow.bold("\u26A0 Deprecation Notice") + `
4458
-
4459
- The ${chalk5.cyan("init")} command is deprecated.
4460
- Please use ${chalk5.cyan("bragduck auth login")} instead.
4461
-
4462
- ` + chalk5.dim("This command will be removed in v3.0.0"),
4463
- {
4464
- padding: 1,
4465
- borderStyle: "round",
4466
- borderColor: "yellow",
4467
- dimBorder: true
4468
- }
4469
- )
4470
- );
4471
- logger.log("");
4472
- logger.info("Starting authentication flow...");
4473
- logger.log("");
4474
- const isAuthenticated = await authService.isAuthenticated();
4475
- if (isAuthenticated) {
4476
- const userInfo = authService.getUserInfo();
4477
- if (userInfo) {
4478
- logger.log(
4479
- boxen3(
4480
- `${chalk5.yellow("Already authenticated!")}
4481
-
4482
- ${chalk5.gray("User:")} ${userInfo.name}
4483
- ${chalk5.gray("Email:")} ${userInfo.email}
4484
-
4485
- ${chalk5.dim("Run")} ${chalk5.cyan("bragduck logout")} ${chalk5.dim("to sign out")}`,
4486
- {
4487
- padding: 1,
4488
- margin: 1,
4489
- borderStyle: "round",
4490
- borderColor: "yellow"
4491
- }
4492
- )
4493
- );
4494
- logger.log("");
4495
- globalThis.setTimeout(() => {
4496
- process.exit(0);
4497
- }, 100);
4498
- return;
4499
- }
4500
- }
4501
- const spinner = ora("Opening browser for authentication...").start();
4502
- try {
4503
- spinner.text = "Waiting for authentication...";
4504
- const userInfo = await authService.login();
4505
- spinner.succeed("Authentication successful!");
4506
- logger.log("");
4507
- logger.log(
4508
- boxen3(
4509
- `${chalk5.green.bold("\u2713 Successfully authenticated!")}
4510
-
4511
- ${chalk5.gray("Welcome,")} ${chalk5.cyan(userInfo.name)}
4512
- ${chalk5.gray("Email:")} ${userInfo.email}
4513
-
4514
- ${chalk5.dim("You can now use")} ${chalk5.cyan("bragduck scan")} ${chalk5.dim("to create brags!")}`,
4515
- {
4516
- padding: 1,
4517
- margin: 1,
4518
- borderStyle: "round",
4519
- borderColor: "green"
4520
- }
4521
- )
4522
- );
4523
- logger.log("");
4524
- globalThis.setTimeout(() => {
4525
- process.exit(0);
4526
- }, 100);
4527
- return;
4528
- } catch (error) {
4529
- spinner.fail("Authentication failed");
4530
- logger.log("");
4531
- const err = error;
4532
- logger.log(
4533
- boxen3(
4534
- `${chalk5.red.bold("\u2717 Authentication Failed")}
4535
-
4536
- ${err.message}
4537
-
4538
- ${chalk5.dim("Hint:")} ${getErrorHint(err)}`,
4539
- {
4540
- padding: 1,
4541
- margin: 1,
4542
- borderStyle: "round",
4543
- borderColor: "red"
4544
- }
4545
- )
4546
- );
4547
- process.exit(1);
4548
- }
4549
- }
4550
- function getErrorHint(error) {
4551
- if (error.name === "OAuthError") {
4552
- if (error.message.includes("timeout")) {
4553
- return "Try again and complete the authentication within 2 minutes";
4554
- }
4555
- if (error.message.includes("CSRF")) {
4556
- return "This might be a security issue. Try running the command again";
4557
- }
4558
- return "Check your internet connection and try again";
4559
- }
4560
- if (error.name === "NetworkError") {
4561
- return "Check your internet connection and firewall settings";
4562
- }
4563
- if (error.name === "AuthenticationError") {
4564
- return "Verify your credentials and try again";
4565
- }
4566
- return "Try running the command again or check the logs with DEBUG=* bragduck init";
4567
- }
4568
-
4569
- // src/utils/auth-helper.ts
4570
- init_logger();
4571
-
4572
- // src/ui/prompts.ts
4573
- init_esm_shims();
4574
- import { checkbox, confirm, input as input2, select as select2, editor } from "@inquirer/prompts";
4575
- import boxen4 from "boxen";
4576
4769
 
4577
4770
  // src/ui/formatters.ts
4578
4771
  init_esm_shims();
@@ -4661,7 +4854,13 @@ function formatRefinedCommitsTable(brags, selectedCommits) {
4661
4854
  } else {
4662
4855
  displaySha = `#${index + 1}`;
4663
4856
  }
4664
- const original = isNewBragType ? typeof brag.original_input === "string" ? brag.original_input.split("\n")[0] || "" : "Raw input" : brag.original_message.split("\n")[0] || "";
4857
+ let original;
4858
+ if (isNewBragType) {
4859
+ const input3 = brag.original_input;
4860
+ original = typeof input3 === "string" ? input3.split("\n")[0] || "" : "Raw input";
4861
+ } else {
4862
+ original = brag.original_message.split("\n")[0] || "";
4863
+ }
4665
4864
  const title = brag.refined_title;
4666
4865
  const description = brag.refined_description.length > 100 ? brag.refined_description.substring(0, 97) + "..." : brag.refined_description;
4667
4866
  const tags = (brag.suggested_tags || []).join(", ") || "none";
@@ -4786,7 +4985,7 @@ async function promptDaysToScan(defaultDays = 30) {
4786
4985
  { name: "90 days", value: "90", description: "Last 3 months" },
4787
4986
  { name: "Custom", value: "custom", description: "Enter custom number of days" }
4788
4987
  ];
4789
- const selected = await select2({
4988
+ const selected = await select3({
4790
4989
  message: "How many days back should we scan for PRs?",
4791
4990
  choices,
4792
4991
  default: "30"
@@ -4820,7 +5019,7 @@ async function promptScanMode() {
4820
5019
  description: "Scan git commits directly"
4821
5020
  }
4822
5021
  ];
4823
- return await select2({
5022
+ return await select3({
4824
5023
  message: "What would you like to scan?",
4825
5024
  choices,
4826
5025
  default: "prs"
@@ -4837,7 +5036,7 @@ async function promptSortOption() {
4837
5036
  { name: "By files (most files)", value: "files", description: "Most files changed" },
4838
5037
  { name: "No sorting", value: "none", description: "Keep original order" }
4839
5038
  ];
4840
- return await select2({
5039
+ return await select3({
4841
5040
  message: "How would you like to sort the PRs?",
4842
5041
  choices,
4843
5042
  default: "date"
@@ -4851,7 +5050,7 @@ async function promptSelectOrganisation(organisations) {
4851
5050
  value: org.id
4852
5051
  }))
4853
5052
  ];
4854
- const selected = await select2({
5053
+ const selected = await select3({
4855
5054
  message: "Attach brags to which company?",
4856
5055
  choices,
4857
5056
  default: "none"
@@ -4914,9 +5113,9 @@ ${theme.label("Impact Score")} ${colors.highlight(impactScore)}`;
4914
5113
 
4915
5114
  ${theme.label("PR Link")} ${colors.link(prUrl)}`;
4916
5115
  }
4917
- console.log(boxen4(bragDetails, boxStyles.info));
5116
+ console.log(boxen3(bragDetails, boxStyles.info));
4918
5117
  console.log("");
4919
- const action = await select2({
5118
+ const action = await select3({
4920
5119
  message: `What would you like to do with this brag?`,
4921
5120
  choices: [
4922
5121
  { name: "\u2713 Accept", value: "accept", description: "Add this brag as-is" },
@@ -4991,7 +5190,7 @@ async function ensureAuthenticated() {
4991
5190
  }
4992
5191
  logger.log("");
4993
5192
  logger.log(
4994
- boxen5(
5193
+ boxen4(
4995
5194
  theme.warning("Not authenticated") + "\n\nYou need to be logged in to use this command.\n\nWould you like to authenticate now?",
4996
5195
  boxStyles.warning
4997
5196
  )
@@ -5007,7 +5206,7 @@ async function ensureAuthenticated() {
5007
5206
  return false;
5008
5207
  }
5009
5208
  try {
5010
- await initCommand();
5209
+ await authCommand("login");
5011
5210
  return true;
5012
5211
  } catch {
5013
5212
  return false;
@@ -5016,9 +5215,9 @@ async function ensureAuthenticated() {
5016
5215
 
5017
5216
  // src/ui/spinners.ts
5018
5217
  init_esm_shims();
5019
- import ora2 from "ora";
5218
+ import ora from "ora";
5020
5219
  function createSpinner(text) {
5021
- return ora2({
5220
+ return ora({
5022
5221
  text,
5023
5222
  color: "cyan",
5024
5223
  spinner: "dots"
@@ -5026,7 +5225,7 @@ function createSpinner(text) {
5026
5225
  }
5027
5226
  function createStepSpinner(currentStep, totalSteps, text) {
5028
5227
  const stepIndicator = theme.step(currentStep, totalSteps);
5029
- return ora2({
5228
+ return ora({
5030
5229
  text: `${stepIndicator} ${text}`,
5031
5230
  color: "cyan",
5032
5231
  spinner: "dots"
@@ -5329,7 +5528,10 @@ async function syncSingleRepository(repo, days, sortOption, scanMode, orgId, opt
5329
5528
  repository: repoInfo.url,
5330
5529
  date: originalCommit?.date || (/* @__PURE__ */ new Date()).toISOString(),
5331
5530
  commit_url: originalCommit?.url || "",
5531
+ impactLevel: refined.suggested_impactLevel,
5532
+ typeId: refined.suggested_typeId,
5332
5533
  impact_score: refined.suggested_impactLevel,
5534
+ impactDescription: refined.impact_description,
5333
5535
  impact_description: refined.impact_description,
5334
5536
  attachments: originalCommit?.url ? [originalCommit.url] : [],
5335
5537
  orgId: orgId || void 0,
@@ -5533,7 +5735,7 @@ async function promptSelectService() {
5533
5735
  description: `Sync all ${authenticatedSyncServices.length} authenticated service${authenticatedSyncServices.length > 1 ? "s" : ""}`
5534
5736
  });
5535
5737
  }
5536
- const selected = await select3({
5738
+ const selected = await select4({
5537
5739
  message: "Select a service to sync:",
5538
5740
  choices: serviceChoices
5539
5741
  });
@@ -5739,7 +5941,10 @@ async function syncSingleService(sourceType, options, TOTAL_STEPS, sharedDays, s
5739
5941
  repository: repoInfo.url,
5740
5942
  date: originalCommit?.date || (/* @__PURE__ */ new Date()).toISOString(),
5741
5943
  commit_url: originalCommit?.url || "",
5944
+ impactLevel: refined.suggested_impactLevel,
5945
+ typeId: refined.suggested_typeId,
5742
5946
  impact_score: refined.suggested_impactLevel,
5947
+ impactDescription: refined.impact_description,
5743
5948
  impact_description: refined.impact_description,
5744
5949
  attachments: originalCommit?.url ? [originalCommit.url] : [],
5745
5950
  orgId: selectedOrgId || void 0,
@@ -5943,10 +6148,10 @@ async function syncAllAuthenticatedServices(options) {
5943
6148
  }
5944
6149
  if (totalCreated > 0) {
5945
6150
  const allCreatedBrags = results.filter((r) => r.createdBrags).flatMap((r) => r.createdBrags);
5946
- logger.log(boxen6(formatSuccessMessage(totalCreated, allCreatedBrags), boxStyles.success));
6151
+ logger.log(boxen5(formatSuccessMessage(totalCreated, allCreatedBrags), boxStyles.success));
5947
6152
  } else if (successful.length > 0) {
5948
6153
  logger.log(
5949
- boxen6(
6154
+ boxen5(
5950
6155
  theme.secondary("No new brags created (all items already exist or were skipped)"),
5951
6156
  boxStyles.info
5952
6157
  )
@@ -5970,7 +6175,7 @@ async function syncCommand(options = {}) {
5970
6175
  logger.debug("FREE tier detected - blocking sync command");
5971
6176
  logger.log("");
5972
6177
  logger.log(
5973
- boxen6(
6178
+ boxen5(
5974
6179
  theme.warning("CLI Access Requires Subscription") + "\n\nThe Bragduck CLI is available for Plus and Pro subscribers.\nUpgrade now to unlock:\n\n \u2022 Automatic work item scanning\n \u2022 AI-powered brag generation\n \u2022 Unlimited brags\n\n" + colors.highlight("Start your free trial today"),
5975
6180
  {
5976
6181
  ...boxStyles.warning,
@@ -6029,7 +6234,7 @@ async function syncCommand(options = {}) {
6029
6234
  const result2 = await syncMultipleRepositories(repos, options);
6030
6235
  if (result2.totalCreated > 0 || result2.totalSkipped > 0) {
6031
6236
  logger.log("");
6032
- logger.log(boxen6(formatMultiRepoSummary(result2), boxStyles.success));
6237
+ logger.log(boxen5(formatMultiRepoSummary(result2), boxStyles.success));
6033
6238
  } else {
6034
6239
  logger.log("");
6035
6240
  logger.info("No brags created.");
@@ -6062,7 +6267,7 @@ async function syncCommand(options = {}) {
6062
6267
  const result = await syncSingleService(sourceType, options, TOTAL_STEPS);
6063
6268
  if (result.created > 0) {
6064
6269
  logger.log(
6065
- boxen6(formatSuccessMessage(result.created, result.createdBrags), boxStyles.success)
6270
+ boxen5(formatSuccessMessage(result.created, result.createdBrags), boxStyles.success)
6066
6271
  );
6067
6272
  } else if (result.skipped > 0) {
6068
6273
  logger.log("");
@@ -6089,12 +6294,12 @@ async function syncCommand(options = {}) {
6089
6294
  const err = error;
6090
6295
  logger.log("");
6091
6296
  logger.log(
6092
- boxen6(formatErrorMessage(err.message, getErrorHint2(err, sourceType)), boxStyles.error)
6297
+ boxen5(formatErrorMessage(err.message, getErrorHint(err, sourceType)), boxStyles.error)
6093
6298
  );
6094
6299
  process.exit(1);
6095
6300
  }
6096
6301
  }
6097
- function getErrorHint2(error, sourceType) {
6302
+ function getErrorHint(error, sourceType) {
6098
6303
  if (error.name === "GitHubError") {
6099
6304
  return 'Make sure you are in a GitHub repository and have authenticated with "gh auth login"';
6100
6305
  }
@@ -6121,17 +6326,17 @@ init_esm_shims();
6121
6326
  init_auth_service();
6122
6327
  init_logger();
6123
6328
  import { confirm as confirm2 } from "@inquirer/prompts";
6124
- import boxen7 from "boxen";
6125
- import chalk6 from "chalk";
6126
- import ora3 from "ora";
6329
+ import boxen6 from "boxen";
6330
+ import chalk5 from "chalk";
6331
+ import ora2 from "ora";
6127
6332
  async function logoutCommand() {
6128
6333
  const isAuthenticated = await authService.isAuthenticated();
6129
6334
  if (!isAuthenticated) {
6130
6335
  logger.log(
6131
- boxen7(
6132
- `${chalk6.yellow("Not currently authenticated")}
6336
+ boxen6(
6337
+ `${chalk5.yellow("Not currently authenticated")}
6133
6338
 
6134
- ${chalk6.dim("Nothing to logout from")}`,
6339
+ ${chalk5.dim("Nothing to logout from")}`,
6135
6340
  {
6136
6341
  padding: 1,
6137
6342
  margin: 1,
@@ -6146,25 +6351,25 @@ ${chalk6.dim("Nothing to logout from")}`,
6146
6351
  const userName = userInfo?.name || "Unknown User";
6147
6352
  logger.log("");
6148
6353
  const shouldLogout = await confirm2({
6149
- message: `Are you sure you want to logout? (${chalk6.cyan(userName)})`,
6354
+ message: `Are you sure you want to logout? (${chalk5.cyan(userName)})`,
6150
6355
  default: false
6151
6356
  });
6152
6357
  if (!shouldLogout) {
6153
6358
  logger.info("Logout cancelled");
6154
6359
  return;
6155
6360
  }
6156
- const spinner = ora3("Logging out...").start();
6361
+ const spinner = ora2("Logging out...").start();
6157
6362
  try {
6158
6363
  await authService.logout();
6159
6364
  spinner.succeed("Logged out successfully");
6160
6365
  logger.log("");
6161
6366
  logger.log(
6162
- boxen7(
6163
- `${chalk6.green.bold("\u2713 Logged out successfully")}
6367
+ boxen6(
6368
+ `${chalk5.green.bold("\u2713 Logged out successfully")}
6164
6369
 
6165
- ${chalk6.dim("Your credentials have been cleared")}
6370
+ ${chalk5.dim("Your credentials have been cleared")}
6166
6371
 
6167
- ${chalk6.dim("Run")} ${chalk6.cyan("bragduck init")} ${chalk6.dim("to login again")}`,
6372
+ ${chalk5.dim("Run")} ${chalk5.cyan("bragduck init")} ${chalk5.dim("to login again")}`,
6168
6373
  {
6169
6374
  padding: 1,
6170
6375
  margin: 1,
@@ -6180,293 +6385,10 @@ ${chalk6.dim("Run")} ${chalk6.cyan("bragduck init")} ${chalk6.dim("to login agai
6180
6385
  }
6181
6386
  }
6182
6387
 
6183
- // src/commands/scan.ts
6184
- init_esm_shims();
6185
- import boxen8 from "boxen";
6186
- import chalk7 from "chalk";
6187
- init_api_service();
6188
- init_storage_service();
6189
- init_logger();
6190
- init_browser();
6191
- init_auth_service();
6192
- async function scanCommand(options = {}) {
6193
- logger.log("");
6194
- logger.log(
6195
- boxen8(
6196
- chalk7.yellow.bold("\u26A0 Deprecation Notice") + `
6197
-
6198
- The ${chalk7.cyan("scan")} command is deprecated.
6199
- Please use ${chalk7.cyan("bragduck sync")} instead.
6200
-
6201
- ` + chalk7.dim("This command will be removed in v3.0.0"),
6202
- {
6203
- padding: 1,
6204
- borderStyle: "round",
6205
- borderColor: "yellow",
6206
- dimBorder: true
6207
- }
6208
- )
6209
- );
6210
- logger.log("");
6211
- const TOTAL_STEPS = 5;
6212
- try {
6213
- const isAuthenticated = await ensureAuthenticated();
6214
- if (!isAuthenticated) {
6215
- process.exit(1);
6216
- }
6217
- logger.debug("Fetching subscription status...");
6218
- const subscriptionStatus = await apiService.getSubscriptionStatus();
6219
- logger.debug("Subscription status response:", JSON.stringify(subscriptionStatus, null, 2));
6220
- logger.debug(
6221
- `Checking tier: "${subscriptionStatus.tier}" (type: ${typeof subscriptionStatus.tier})`
6222
- );
6223
- logger.debug(`Tier === 'FREE': ${subscriptionStatus.tier === "FREE"}`);
6224
- if (subscriptionStatus.tier === "FREE") {
6225
- logger.debug("FREE tier detected - blocking scan command");
6226
- logger.log("");
6227
- logger.log(
6228
- boxen8(
6229
- theme.warning("CLI Access Requires Subscription") + "\n\nThe Bragduck CLI is available for Plus and Pro subscribers.\nUpgrade now to unlock:\n\n \u2022 Automatic PR scanning\n \u2022 AI-powered brag generation\n \u2022 Unlimited brags\n\n" + colors.highlight("Start your free trial today!"),
6230
- {
6231
- ...boxStyles.warning,
6232
- padding: 1,
6233
- margin: 1
6234
- }
6235
- )
6236
- );
6237
- logger.log("");
6238
- const shouldOpenBrowser = await promptConfirm(
6239
- "Open subscription plans in your browser?",
6240
- true
6241
- );
6242
- if (shouldOpenBrowser) {
6243
- logger.log("");
6244
- const plansUrl = "https://bragduck.com/app/settings/plans";
6245
- try {
6246
- await openBrowser(plansUrl);
6247
- logger.info(theme.secondary("Opening browser..."));
6248
- } catch {
6249
- logger.warning("Could not open browser automatically");
6250
- logger.info(`Please visit: ${theme.value(plansUrl)}`);
6251
- }
6252
- }
6253
- logger.log("");
6254
- return;
6255
- }
6256
- logger.debug(`Subscription tier "${subscriptionStatus.tier}" - proceeding with scan`);
6257
- const repoSpinner = createStepSpinner(1, TOTAL_STEPS, "Validating GitHub repository");
6258
- repoSpinner.start();
6259
- await githubService.validateGitHubRepository();
6260
- const repoInfo = await githubService.getRepositoryInfo();
6261
- succeedStepSpinner(
6262
- repoSpinner,
6263
- 1,
6264
- TOTAL_STEPS,
6265
- `Repository: ${theme.value(repoInfo.fullName)}`
6266
- );
6267
- logger.log("");
6268
- let days = options.days;
6269
- if (!days) {
6270
- const defaultDays = storageService.getConfig("defaultCommitDays");
6271
- days = await promptDaysToScan(defaultDays);
6272
- logger.log("");
6273
- }
6274
- const prSpinner = createStepSpinner(
6275
- 2,
6276
- TOTAL_STEPS,
6277
- `Fetching merged PRs from the last ${days} days`
6278
- );
6279
- prSpinner.start();
6280
- let prs;
6281
- if (options.all) {
6282
- prs = await githubService.getMergedPRs({ days });
6283
- } else {
6284
- prs = await githubService.getPRsByCurrentUser({ days });
6285
- }
6286
- const commits = prs.map((pr) => githubService.transformPRToCommit(pr));
6287
- if (commits.length === 0) {
6288
- failStepSpinner(prSpinner, 2, TOTAL_STEPS, `No merged PRs found in the last ${days} days`);
6289
- logger.log("");
6290
- logger.info("Try increasing the number of days or check your GitHub activity");
6291
- return;
6292
- }
6293
- succeedStepSpinner(
6294
- prSpinner,
6295
- 2,
6296
- TOTAL_STEPS,
6297
- `Found ${theme.count(commits.length)} PR${commits.length > 1 ? "s" : ""}`
6298
- );
6299
- logger.log("");
6300
- logger.log(formatCommitStats(commits));
6301
- logger.log("");
6302
- let sortedCommits = [...commits];
6303
- if (commits.length > 1) {
6304
- const sortOption = await promptSortOption();
6305
- logger.log("");
6306
- if (sortOption === "date") {
6307
- sortedCommits.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
6308
- } else if (sortOption === "size") {
6309
- sortedCommits.sort((a, b) => {
6310
- const sizeA = (a.diffStats?.insertions || 0) + (a.diffStats?.deletions || 0);
6311
- const sizeB = (b.diffStats?.insertions || 0) + (b.diffStats?.deletions || 0);
6312
- return sizeB - sizeA;
6313
- });
6314
- } else if (sortOption === "files") {
6315
- sortedCommits.sort((a, b) => {
6316
- const filesA = a.diffStats?.filesChanged || 0;
6317
- const filesB = b.diffStats?.filesChanged || 0;
6318
- return filesB - filesA;
6319
- });
6320
- }
6321
- }
6322
- const selectedShas = await promptSelectCommits(sortedCommits);
6323
- if (selectedShas.length === 0) {
6324
- logger.log("");
6325
- logger.info(theme.secondary("No PRs selected. Scan cancelled."));
6326
- logger.log("");
6327
- return;
6328
- }
6329
- const selectedCommits = sortedCommits.filter((c) => selectedShas.includes(c.sha));
6330
- logger.log(formatSelectionSummary(selectedCommits.length, selectedCommits));
6331
- logger.log("");
6332
- const existingBrags = await apiService.listBrags({ limit: 100 });
6333
- logger.debug(`Fetched ${existingBrags.brags.length} existing brags`);
6334
- const existingUrls = new Set(existingBrags.brags.flatMap((b) => b.attachments || []));
6335
- logger.debug(`Existing PR URLs in attachments: ${existingUrls.size}`);
6336
- const duplicates = selectedCommits.filter((c) => c.url && existingUrls.has(c.url));
6337
- const newCommits = selectedCommits.filter((c) => !c.url || !existingUrls.has(c.url));
6338
- logger.debug(`Duplicates: ${duplicates.length}, New: ${newCommits.length}`);
6339
- if (duplicates.length > 0) {
6340
- logger.log("");
6341
- logger.info(
6342
- colors.warning(
6343
- `${duplicates.length} PR${duplicates.length > 1 ? "s" : ""} already added to Bragduck - skipping`
6344
- )
6345
- );
6346
- logger.log("");
6347
- }
6348
- if (newCommits.length === 0) {
6349
- logger.log("");
6350
- logger.info(
6351
- theme.secondary("All selected PRs already exist in Bragduck. Nothing to refine.")
6352
- );
6353
- logger.log("");
6354
- return;
6355
- }
6356
- const refineSpinner = createStepSpinner(
6357
- 3,
6358
- TOTAL_STEPS,
6359
- `Refining ${theme.count(newCommits.length)} PR${newCommits.length > 1 ? "s" : ""} with AI`
6360
- );
6361
- refineSpinner.start();
6362
- const refineRequest = {
6363
- brags: newCommits.map((c) => ({
6364
- text: c.message,
6365
- date: c.date,
6366
- title: c.message.split("\n")[0]
6367
- // First line as initial title
6368
- }))
6369
- };
6370
- const refineResponse = await apiService.refineBrags(refineRequest);
6371
- let refinedBrags = refineResponse.refined_brags;
6372
- succeedStepSpinner(refineSpinner, 3, TOTAL_STEPS, "PRs refined successfully");
6373
- logger.log("");
6374
- logger.info("Preview of refined brags:");
6375
- logger.log("");
6376
- logger.log(formatRefinedCommitsTable(refinedBrags, newCommits));
6377
- logger.log("");
6378
- const acceptedBrags = await promptReviewBrags(refinedBrags, newCommits);
6379
- if (acceptedBrags.length === 0) {
6380
- logger.log("");
6381
- logger.info(theme.secondary("No brags selected for creation. Scan cancelled."));
6382
- logger.log("");
6383
- return;
6384
- }
6385
- logger.log("");
6386
- let selectedOrgId = null;
6387
- const userInfo = authService.getUserInfo();
6388
- if (userInfo?.id) {
6389
- try {
6390
- const orgsResponse = await apiService.listUserOrganisations(userInfo.id);
6391
- if (orgsResponse.items.length > 0) {
6392
- selectedOrgId = await promptSelectOrganisation(orgsResponse.items);
6393
- logger.log("");
6394
- }
6395
- } catch {
6396
- logger.debug("Failed to fetch organisations, skipping org selection");
6397
- }
6398
- }
6399
- const createSpinner2 = createStepSpinner(
6400
- 4,
6401
- TOTAL_STEPS,
6402
- `Creating ${theme.count(acceptedBrags.length)} brag${acceptedBrags.length > 1 ? "s" : ""}`
6403
- );
6404
- createSpinner2.start();
6405
- const createRequest = {
6406
- brags: acceptedBrags.map((refined, index) => {
6407
- const originalCommit = newCommits[index];
6408
- const repoTag = repoInfo.name;
6409
- const tagsWithRepo = refined.suggested_tags.includes(repoTag) ? refined.suggested_tags : [repoTag, ...refined.suggested_tags];
6410
- return {
6411
- commit_sha: originalCommit?.sha || `brag-${index}`,
6412
- title: refined.refined_title,
6413
- description: refined.refined_description,
6414
- tags: tagsWithRepo,
6415
- repository: repoInfo.url,
6416
- date: originalCommit?.date || "",
6417
- commit_url: originalCommit?.url || "",
6418
- impact_score: refined.suggested_impactLevel,
6419
- impact_description: refined.impact_description,
6420
- attachments: originalCommit?.url ? [originalCommit.url] : [],
6421
- orgId: selectedOrgId || void 0
6422
- };
6423
- })
6424
- };
6425
- const createResponse = await apiService.createBrags(createRequest);
6426
- succeedStepSpinner(
6427
- createSpinner2,
6428
- 4,
6429
- TOTAL_STEPS,
6430
- `Created ${theme.count(createResponse.created)} brag${createResponse.created > 1 ? "s" : ""}`
6431
- );
6432
- logger.log("");
6433
- logger.log(boxen8(formatSuccessMessage(createResponse.created), boxStyles.success));
6434
- } catch (error) {
6435
- if (error instanceof CancelPromptError) {
6436
- logger.log("");
6437
- logger.info(theme.secondary("Scan cancelled."));
6438
- logger.log("");
6439
- return;
6440
- }
6441
- const err = error;
6442
- logger.log("");
6443
- logger.log(boxen8(formatErrorMessage(err.message, getErrorHint3(err)), boxStyles.error));
6444
- process.exit(1);
6445
- }
6446
- }
6447
- function getErrorHint3(error) {
6448
- if (error.name === "GitHubError") {
6449
- return 'Make sure you are in a GitHub repository and have authenticated with "gh auth login"';
6450
- }
6451
- if (error.name === "GitError") {
6452
- return "Make sure you are in a git repository. Note: Only GitHub repositories are supported for PR scanning.";
6453
- }
6454
- if (error.name === "TokenExpiredError" || error.name === "AuthenticationError") {
6455
- return 'Run "bragduck init" to login again';
6456
- }
6457
- if (error.name === "NetworkError") {
6458
- return "Check your internet connection and try again";
6459
- }
6460
- if (error.name === "ApiError") {
6461
- return "The server might be experiencing issues. Try again later";
6462
- }
6463
- return "Try running with DEBUG=* for more information";
6464
- }
6465
-
6466
6388
  // src/commands/list.ts
6467
6389
  init_esm_shims();
6468
6390
  init_api_service();
6469
- import boxen9 from "boxen";
6391
+ import boxen7 from "boxen";
6470
6392
  import Table2 from "cli-table3";
6471
6393
  init_logger();
6472
6394
  async function listCommand(options = {}) {
@@ -6533,7 +6455,7 @@ async function listCommand(options = {}) {
6533
6455
  } catch (error) {
6534
6456
  const err = error;
6535
6457
  logger.log("");
6536
- logger.log(boxen9(formatErrorMessage(err.message, getErrorHint4(err)), boxStyles.error));
6458
+ logger.log(boxen7(formatErrorMessage(err.message, getErrorHint2(err)), boxStyles.error));
6537
6459
  process.exit(1);
6538
6460
  }
6539
6461
  }
@@ -6555,7 +6477,7 @@ function formatBragsTable(brags) {
6555
6477
  const title = brag.title;
6556
6478
  const description = truncateText(brag.description, 100);
6557
6479
  const tags = brag.tags.length > 0 ? brag.tags.join(", ") : colors.dim("none");
6558
- const repository = brag.repository ? truncateText(extractRepoName(brag.repository), 25) : colors.dim("none");
6480
+ const repository = brag.externalUrl ? truncateText(extractRepoName(brag.externalUrl), 25) : colors.dim("none");
6559
6481
  table.push([
6560
6482
  colors.highlight(date),
6561
6483
  colors.white(title),
@@ -6586,7 +6508,7 @@ function extractRepoName(url) {
6586
6508
  return url;
6587
6509
  }
6588
6510
  }
6589
- function getErrorHint4(error) {
6511
+ function getErrorHint2(error) {
6590
6512
  if (error.name === "TokenExpiredError" || error.name === "AuthenticationError") {
6591
6513
  return 'Run "bragduck init" to login again';
6592
6514
  }
@@ -6604,8 +6526,8 @@ init_esm_shims();
6604
6526
  init_storage_service();
6605
6527
  init_logger();
6606
6528
  init_constants();
6607
- import boxen10 from "boxen";
6608
- import chalk8 from "chalk";
6529
+ import boxen8 from "boxen";
6530
+ import chalk6 from "chalk";
6609
6531
  import Table3 from "cli-table3";
6610
6532
  init_errors();
6611
6533
  var VALID_CONFIG_KEYS = Object.values(CONFIG_KEYS);
@@ -6640,7 +6562,7 @@ async function configCommand(subcommand, key, value) {
6640
6562
  const err = error;
6641
6563
  logger.log("");
6642
6564
  logger.log(
6643
- boxen10(formatErrorMessage(err.message, getConfigHint(err)), {
6565
+ boxen8(formatErrorMessage(err.message, getConfigHint(err)), {
6644
6566
  padding: 1,
6645
6567
  margin: 1,
6646
6568
  borderStyle: "round",
@@ -6661,7 +6583,7 @@ async function handleListConfig() {
6661
6583
  gitlabInstance: storageService.getConfig("gitlabInstance")
6662
6584
  };
6663
6585
  const table = new Table3({
6664
- head: [chalk8.cyan("Key"), chalk8.cyan("Value"), chalk8.cyan("Default")],
6586
+ head: [chalk6.cyan("Key"), chalk6.cyan("Value"), chalk6.cyan("Default")],
6665
6587
  colWidths: [25, 40, 40],
6666
6588
  wordWrap: true,
6667
6589
  style: {
@@ -6670,61 +6592,61 @@ async function handleListConfig() {
6670
6592
  }
6671
6593
  });
6672
6594
  table.push([
6673
- chalk8.white("defaultCommitDays"),
6674
- chalk8.yellow(String(config2.defaultCommitDays)),
6675
- chalk8.dim(String(DEFAULT_CONFIG.defaultCommitDays))
6595
+ chalk6.white("defaultCommitDays"),
6596
+ chalk6.yellow(String(config2.defaultCommitDays)),
6597
+ chalk6.dim(String(DEFAULT_CONFIG.defaultCommitDays))
6676
6598
  ]);
6677
6599
  table.push([
6678
- chalk8.white("autoVersionCheck"),
6679
- chalk8.yellow(String(config2.autoVersionCheck)),
6680
- chalk8.dim(String(DEFAULT_CONFIG.autoVersionCheck))
6600
+ chalk6.white("autoVersionCheck"),
6601
+ chalk6.yellow(String(config2.autoVersionCheck)),
6602
+ chalk6.dim(String(DEFAULT_CONFIG.autoVersionCheck))
6681
6603
  ]);
6682
6604
  table.push([
6683
- chalk8.white("defaultSource"),
6684
- chalk8.yellow(config2.defaultSource || "not set"),
6685
- chalk8.dim("not set")
6605
+ chalk6.white("defaultSource"),
6606
+ chalk6.yellow(config2.defaultSource || "not set"),
6607
+ chalk6.dim("not set")
6686
6608
  ]);
6687
6609
  table.push([
6688
- chalk8.white("sourcePriority"),
6689
- chalk8.yellow(config2.sourcePriority ? config2.sourcePriority.join(", ") : "not set"),
6690
- chalk8.dim("not set")
6610
+ chalk6.white("sourcePriority"),
6611
+ chalk6.yellow(config2.sourcePriority ? config2.sourcePriority.join(", ") : "not set"),
6612
+ chalk6.dim("not set")
6691
6613
  ]);
6692
6614
  table.push([
6693
- chalk8.white("jiraInstance"),
6694
- chalk8.yellow(config2.jiraInstance || "not set"),
6695
- chalk8.dim("not set")
6615
+ chalk6.white("jiraInstance"),
6616
+ chalk6.yellow(config2.jiraInstance || "not set"),
6617
+ chalk6.dim("not set")
6696
6618
  ]);
6697
6619
  table.push([
6698
- chalk8.white("confluenceInstance"),
6699
- chalk8.yellow(config2.confluenceInstance || "not set"),
6700
- chalk8.dim("not set")
6620
+ chalk6.white("confluenceInstance"),
6621
+ chalk6.yellow(config2.confluenceInstance || "not set"),
6622
+ chalk6.dim("not set")
6701
6623
  ]);
6702
6624
  table.push([
6703
- chalk8.white("gitlabInstance"),
6704
- chalk8.yellow(config2.gitlabInstance || "not set"),
6705
- chalk8.dim("not set")
6625
+ chalk6.white("gitlabInstance"),
6626
+ chalk6.yellow(config2.gitlabInstance || "not set"),
6627
+ chalk6.dim("not set")
6706
6628
  ]);
6707
6629
  logger.info("Current configuration:");
6708
6630
  logger.log("");
6709
6631
  logger.log(table.toString());
6710
6632
  logger.log("");
6711
- logger.info(chalk8.dim("To change a value: ") + chalk8.cyan("bragduck config set <key> <value>"));
6633
+ logger.info(chalk6.dim("To change a value: ") + chalk6.cyan("bragduck config set <key> <value>"));
6712
6634
  logger.log("");
6713
6635
  }
6714
6636
  async function handleGetConfig(key) {
6715
6637
  validateConfigKey(key);
6716
6638
  const value = storageService.getConfig(key);
6717
6639
  const defaultValue = DEFAULT_CONFIG[key];
6718
- logger.info(`Configuration for ${chalk8.cyan(key)}:`);
6640
+ logger.info(`Configuration for ${chalk6.cyan(key)}:`);
6719
6641
  logger.log("");
6720
- logger.log(` ${chalk8.white("Current:")} ${chalk8.yellow(String(value))}`);
6721
- logger.log(` ${chalk8.white("Default:")} ${chalk8.dim(String(defaultValue))}`);
6642
+ logger.log(` ${chalk6.white("Current:")} ${chalk6.yellow(String(value))}`);
6643
+ logger.log(` ${chalk6.white("Default:")} ${chalk6.dim(String(defaultValue))}`);
6722
6644
  logger.log("");
6723
6645
  if (value === defaultValue) {
6724
- logger.info(chalk8.dim("Using default value"));
6646
+ logger.info(chalk6.dim("Using default value"));
6725
6647
  } else {
6726
6648
  logger.info(
6727
- chalk8.dim("Custom value set. Reset with: ") + chalk8.cyan(`bragduck config set ${key} ${defaultValue}`)
6649
+ chalk6.dim("Custom value set. Reset with: ") + chalk6.cyan(`bragduck config set ${key} ${defaultValue}`)
6728
6650
  );
6729
6651
  }
6730
6652
  logger.log("");
@@ -6734,10 +6656,10 @@ async function handleSetConfig(key, value) {
6734
6656
  const typedValue = validateAndConvertValue(key, value);
6735
6657
  storageService.setConfig(key, typedValue);
6736
6658
  logger.log(
6737
- boxen10(
6738
- `${chalk8.green.bold("\u2713 Configuration updated")}
6659
+ boxen8(
6660
+ `${chalk6.green.bold("\u2713 Configuration updated")}
6739
6661
 
6740
- ${chalk8.white(key)}: ${chalk8.yellow(String(typedValue))}`,
6662
+ ${chalk6.white(key)}: ${chalk6.yellow(String(typedValue))}`,
6741
6663
  {
6742
6664
  padding: 1,
6743
6665
  margin: 1,
@@ -6834,12 +6756,12 @@ function getConfigHint(error) {
6834
6756
  // src/cli.ts
6835
6757
  init_version();
6836
6758
  init_logger();
6837
- var __filename6 = fileURLToPath6(import.meta.url);
6838
- var __dirname6 = dirname5(__filename6);
6839
- var packageJsonPath = join7(__dirname6, "../../package.json");
6840
- var packageJson = JSON.parse(readFileSync5(packageJsonPath, "utf-8"));
6759
+ var __filename7 = fileURLToPath7(import.meta.url);
6760
+ var __dirname7 = dirname6(__filename7);
6761
+ var packageJsonPath = join8(__dirname7, "../../package.json");
6762
+ var packageJson = JSON.parse(readFileSync6(packageJsonPath, "utf-8"));
6841
6763
  var program = new Command();
6842
- program.name("bragduck").description("CLI tool for managing developer achievements and brags\nAliases: bd, duck, brag").version(packageJson.version, "-v, --version", "Display version number").helpOption("-h, --help", "Display help information").option("--skip-version-check", "Skip automatic version check on startup").option("--debug", "Enable debug mode (shows detailed logs)");
6764
+ program.name("bragduck").description("CLI tool for managing developer achievements and brags\nAliases: duck, brag").version(packageJson.version, "-v, --version", "Display version number").helpOption("-h, --help", "Display help information").option("--skip-version-check", "Skip automatic version check on startup").option("--debug", "Enable debug mode (shows detailed logs)");
6843
6765
  program.command("auth [subcommand]").description("Manage authentication (subcommands: login, status, bitbucket, gitlab)").action(async (subcommand) => {
6844
6766
  try {
6845
6767
  await authCommand(subcommand);
@@ -6856,22 +6778,6 @@ program.command("sync").description("Sync work items and create brags").option("
6856
6778
  process.exit(1);
6857
6779
  }
6858
6780
  });
6859
- program.command("init").description("Authenticate with Bragduck").action(async () => {
6860
- try {
6861
- await initCommand();
6862
- } catch (error) {
6863
- console.error(error);
6864
- process.exit(1);
6865
- }
6866
- });
6867
- program.command("scan").description("Scan git commits and create brags (deprecated, use sync)").option("-d, --days <number>", "Number of days to scan", (val) => parseInt(val, 10)).option("-a, --all", "Include all commits (not just current user)").action(async (options) => {
6868
- try {
6869
- await scanCommand(options);
6870
- } catch (error) {
6871
- console.error(error);
6872
- process.exit(1);
6873
- }
6874
- });
6875
6781
  program.command("list").description("List your existing brags").option("-l, --limit <number>", "Number of brags to display", (val) => parseInt(val, 10), 50).option("-o, --offset <number>", "Number of brags to skip", (val) => parseInt(val, 10), 0).option("-t, --tags <tags>", "Filter by tags (comma-separated)").option("-s, --search <query>", "Search brags by keyword").option("--oneline", "Show only date and title for each brag").action(async (options) => {
6876
6782
  try {
6877
6783
  await listCommand(options);