@bragduck/cli 2.3.11 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -232,6 +232,82 @@ var init_storage_service = __esm({
232
232
  }
233
233
  return true;
234
234
  }
235
+ /**
236
+ * Get credentials for a specific service
237
+ */
238
+ async getServiceCredentials(service) {
239
+ const credentials = await this.getCredentials();
240
+ if (!credentials) return null;
241
+ if (service === "bragduck" && credentials.accessToken && !credentials.services) {
242
+ return {
243
+ accessToken: credentials.accessToken,
244
+ refreshToken: credentials.refreshToken,
245
+ expiresAt: credentials.expiresAt
246
+ };
247
+ }
248
+ return credentials.services?.[service] || null;
249
+ }
250
+ /**
251
+ * Set credentials for a specific service
252
+ */
253
+ async setServiceCredentials(service, creds) {
254
+ let existing = await this.getCredentials();
255
+ if (existing && existing.accessToken && !existing.services) {
256
+ existing = {
257
+ services: {
258
+ bragduck: {
259
+ accessToken: existing.accessToken,
260
+ refreshToken: existing.refreshToken,
261
+ expiresAt: existing.expiresAt
262
+ }
263
+ }
264
+ };
265
+ }
266
+ const updated = {
267
+ ...existing,
268
+ services: {
269
+ ...existing?.services,
270
+ [service]: creds
271
+ }
272
+ };
273
+ await this.setCredentials(updated);
274
+ }
275
+ /**
276
+ * Delete credentials for a specific service
277
+ */
278
+ async deleteServiceCredentials(service) {
279
+ const existing = await this.getCredentials();
280
+ if (!existing?.services?.[service]) return;
281
+ delete existing.services[service];
282
+ await this.setCredentials(existing);
283
+ }
284
+ /**
285
+ * Check if a specific service is authenticated
286
+ */
287
+ async isServiceAuthenticated(service) {
288
+ const creds = await this.getServiceCredentials(service);
289
+ if (!creds?.accessToken) return false;
290
+ if (creds.expiresAt && creds.expiresAt < Date.now()) return false;
291
+ return true;
292
+ }
293
+ /**
294
+ * List all authenticated services
295
+ */
296
+ async getAuthenticatedServices() {
297
+ const credentials = await this.getCredentials();
298
+ if (!credentials) return [];
299
+ if (credentials.accessToken && !credentials.services) {
300
+ return ["bragduck"];
301
+ }
302
+ if (!credentials.services) return [];
303
+ const services = [];
304
+ for (const [service, creds] of Object.entries(credentials.services)) {
305
+ if (creds?.accessToken) {
306
+ services.push(service);
307
+ }
308
+ }
309
+ return services;
310
+ }
235
311
  /**
236
312
  * Store user information
237
313
  */
@@ -316,7 +392,7 @@ var init_storage_service = __esm({
316
392
  });
317
393
 
318
394
  // src/utils/errors.ts
319
- var BragduckError, AuthenticationError, GitError, ApiError, NetworkError, ValidationError, OAuthError, TokenExpiredError, GitHubError;
395
+ var BragduckError, AuthenticationError, GitError, ApiError, NetworkError, ValidationError, OAuthError, TokenExpiredError, GitHubError, BitbucketError;
320
396
  var init_errors = __esm({
321
397
  "src/utils/errors.ts"() {
322
398
  "use strict";
@@ -383,6 +459,12 @@ var init_errors = __esm({
383
459
  this.name = "GitHubError";
384
460
  }
385
461
  };
462
+ BitbucketError = class extends BragduckError {
463
+ constructor(message, details) {
464
+ super(message, "BITBUCKET_ERROR", details);
465
+ this.name = "BitbucketError";
466
+ }
467
+ };
386
468
  }
387
469
  });
388
470
 
@@ -932,12 +1014,12 @@ __export(version_exports, {
932
1014
  });
933
1015
  import { readFileSync as readFileSync3 } from "fs";
934
1016
  import { fileURLToPath as fileURLToPath4 } from "url";
935
- import { dirname as dirname3, join as join5 } from "path";
1017
+ import { dirname as dirname3, join as join4 } from "path";
936
1018
  import chalk4 from "chalk";
937
- import boxen3 from "boxen";
1019
+ import boxen2 from "boxen";
938
1020
  function getCurrentVersion() {
939
1021
  try {
940
- const packageJsonPath2 = join5(__dirname4, "../../package.json");
1022
+ const packageJsonPath2 = join4(__dirname4, "../../package.json");
941
1023
  const packageJson2 = JSON.parse(readFileSync3(packageJsonPath2, "utf-8"));
942
1024
  return packageJson2.version;
943
1025
  } catch {
@@ -1003,7 +1085,7 @@ Latest version: ${chalk4.green(latestVersion)}
1003
1085
  Update with: ${chalk4.cyan("npm install -g @bragduck/cli@latest")}`;
1004
1086
  console.log("");
1005
1087
  console.log(
1006
- boxen3(message, {
1088
+ boxen2(message, {
1007
1089
  padding: 1,
1008
1090
  margin: { top: 0, right: 1, bottom: 0, left: 1 },
1009
1091
  borderStyle: "round",
@@ -1039,10 +1121,10 @@ import { ofetch as ofetch2 } from "ofetch";
1039
1121
  import { readFileSync as readFileSync4 } from "fs";
1040
1122
  import { fileURLToPath as fileURLToPath5 } from "url";
1041
1123
  import { URLSearchParams as URLSearchParams2 } from "url";
1042
- import { dirname as dirname4, join as join6 } from "path";
1124
+ import { dirname as dirname4, join as join5 } from "path";
1043
1125
  function getCliVersion() {
1044
1126
  try {
1045
- const packageJsonPath2 = join6(__dirname5, "../../package.json");
1127
+ const packageJsonPath2 = join5(__dirname5, "../../package.json");
1046
1128
  const packageJson2 = JSON.parse(readFileSync4(packageJsonPath2, "utf-8"));
1047
1129
  return packageJson2.version;
1048
1130
  } catch {
@@ -1354,29 +1436,205 @@ import { readFileSync as readFileSync5 } from "fs";
1354
1436
  import { fileURLToPath as fileURLToPath6 } from "url";
1355
1437
  import { dirname as dirname5, join as join7 } from "path";
1356
1438
 
1357
- // src/commands/init.ts
1439
+ // src/commands/auth.ts
1358
1440
  init_esm_shims();
1359
1441
  init_auth_service();
1442
+ init_storage_service();
1360
1443
  init_logger();
1361
- import ora from "ora";
1362
1444
  import boxen from "boxen";
1445
+ import chalk3 from "chalk";
1446
+ import { input } from "@inquirer/prompts";
1447
+
1448
+ // src/ui/theme.ts
1449
+ init_esm_shims();
1363
1450
  import chalk2 from "chalk";
1364
- async function initCommand() {
1451
+ var colors = {
1452
+ // Primary colors for main actions and interactive elements
1453
+ primary: chalk2.cyan,
1454
+ // Success states
1455
+ success: chalk2.green,
1456
+ successBold: chalk2.green.bold,
1457
+ // Warning states
1458
+ warning: chalk2.yellow,
1459
+ warningBold: chalk2.yellow.bold,
1460
+ // Error states
1461
+ error: chalk2.red,
1462
+ errorBold: chalk2.red.bold,
1463
+ // Info and metadata
1464
+ info: chalk2.gray,
1465
+ infoDim: chalk2.dim,
1466
+ // Highlighted/important data
1467
+ highlight: chalk2.yellow.bold,
1468
+ highlightCyan: chalk2.cyan.bold,
1469
+ // Text emphasis
1470
+ bold: chalk2.bold,
1471
+ dim: chalk2.dim,
1472
+ // Special purpose
1473
+ white: chalk2.white,
1474
+ gray: chalk2.gray,
1475
+ // Links and URLs
1476
+ link: chalk2.blue.underline
1477
+ };
1478
+ var theme = {
1479
+ /**
1480
+ * Format command names and CLI actions
1481
+ */
1482
+ command: (text) => colors.primary(text),
1483
+ /**
1484
+ * Format success messages
1485
+ */
1486
+ success: (text) => colors.success(`\u2713 ${text}`),
1487
+ successBold: (text) => colors.successBold(`\u2713 ${text}`),
1488
+ /**
1489
+ * Format error messages
1490
+ */
1491
+ error: (text) => colors.error(`\u2717 ${text}`),
1492
+ errorBold: (text) => colors.errorBold(`\u2717 ${text}`),
1493
+ /**
1494
+ * Format warning messages
1495
+ */
1496
+ warning: (text) => colors.warning(`\u26A0 ${text}`),
1497
+ /**
1498
+ * Format info messages
1499
+ */
1500
+ info: (text) => colors.info(text),
1501
+ /**
1502
+ * Format labels (e.g., "User:", "Email:")
1503
+ */
1504
+ label: (text) => colors.gray(`${text}:`),
1505
+ /**
1506
+ * Format values
1507
+ */
1508
+ value: (text) => colors.highlight(text),
1509
+ /**
1510
+ * Format counts and numbers
1511
+ */
1512
+ count: (num) => colors.highlightCyan(num.toString()),
1513
+ /**
1514
+ * Format file paths
1515
+ */
1516
+ path: (text) => colors.info(text),
1517
+ /**
1518
+ * Format step indicators (e.g., "Step 1/5:")
1519
+ */
1520
+ step: (current, total) => colors.primary(`[${current}/${total}]`),
1521
+ /**
1522
+ * Format progress text
1523
+ */
1524
+ progress: (text) => colors.infoDim(text),
1525
+ /**
1526
+ * Format emphasized text
1527
+ */
1528
+ emphasis: (text) => colors.bold(text),
1529
+ /**
1530
+ * Format de-emphasized/secondary text
1531
+ */
1532
+ secondary: (text) => colors.dim(text),
1533
+ /**
1534
+ * Format interactive elements (prompts, selections)
1535
+ */
1536
+ interactive: (text) => colors.primary(text),
1537
+ /**
1538
+ * Format keys in key-value pairs
1539
+ */
1540
+ key: (text) => colors.white(text)
1541
+ };
1542
+ var boxStyles = {
1543
+ success: {
1544
+ borderColor: "green",
1545
+ borderStyle: "round",
1546
+ padding: 1,
1547
+ margin: 1
1548
+ },
1549
+ error: {
1550
+ borderColor: "red",
1551
+ borderStyle: "round",
1552
+ padding: 1,
1553
+ margin: 1
1554
+ },
1555
+ warning: {
1556
+ borderColor: "yellow",
1557
+ borderStyle: "round",
1558
+ padding: 1,
1559
+ margin: 1
1560
+ },
1561
+ info: {
1562
+ borderColor: "cyan",
1563
+ borderStyle: "round",
1564
+ padding: 1,
1565
+ margin: 1
1566
+ },
1567
+ plain: {
1568
+ borderColor: "gray",
1569
+ borderStyle: "round",
1570
+ padding: 1,
1571
+ margin: 1
1572
+ }
1573
+ };
1574
+ var tableStyles = {
1575
+ default: {
1576
+ style: {
1577
+ head: [],
1578
+ border: ["gray"]
1579
+ }
1580
+ },
1581
+ compact: {
1582
+ style: {
1583
+ head: [],
1584
+ border: ["dim"],
1585
+ compact: true
1586
+ }
1587
+ }
1588
+ };
1589
+ var sizeIndicators = {
1590
+ small: colors.success("\u25CF"),
1591
+ medium: colors.warning("\u25CF"),
1592
+ large: colors.error("\u25CF")
1593
+ };
1594
+ function getSizeIndicator(insertions, deletions) {
1595
+ const total = insertions + deletions;
1596
+ if (total < 50) {
1597
+ return `${sizeIndicators.small} Small`;
1598
+ } else if (total < 200) {
1599
+ return `${sizeIndicators.medium} Medium`;
1600
+ } else {
1601
+ return `${sizeIndicators.large} Large`;
1602
+ }
1603
+ }
1604
+ function formatDiffStats(insertions, deletions) {
1605
+ return `${colors.success(`+${insertions}`)} ${colors.error(`-${deletions}`)}`;
1606
+ }
1607
+
1608
+ // src/commands/auth.ts
1609
+ async function authCommand(subcommand) {
1610
+ if (!subcommand || subcommand === "login") {
1611
+ await authLogin();
1612
+ } else if (subcommand === "status") {
1613
+ await authStatus();
1614
+ } else if (subcommand === "bitbucket") {
1615
+ await authBitbucket();
1616
+ } else {
1617
+ logger.error(`Unknown auth subcommand: ${subcommand}`);
1618
+ logger.info("Available subcommands: login, status, bitbucket");
1619
+ process.exit(1);
1620
+ }
1621
+ }
1622
+ async function authLogin() {
1365
1623
  logger.log("");
1366
- logger.info("Starting authentication flow...");
1624
+ logger.info("Authenticating with Bragduck...");
1367
1625
  logger.log("");
1368
- const isAuthenticated = await authService.isAuthenticated();
1626
+ const isAuthenticated = await storageService.isServiceAuthenticated("bragduck");
1369
1627
  if (isAuthenticated) {
1370
1628
  const userInfo = authService.getUserInfo();
1371
1629
  if (userInfo) {
1372
1630
  logger.log(
1373
1631
  boxen(
1374
- `${chalk2.yellow("Already authenticated!")}
1632
+ `${chalk3.yellow("Already authenticated!")}
1375
1633
 
1376
- ${chalk2.gray("User:")} ${userInfo.name}
1377
- ${chalk2.gray("Email:")} ${userInfo.email}
1634
+ ${chalk3.gray("User:")} ${userInfo.name}
1635
+ ${chalk3.gray("Email:")} ${userInfo.email}
1378
1636
 
1379
- ${chalk2.dim("Run")} ${chalk2.cyan("bragduck logout")} ${chalk2.dim("to sign out")}`,
1637
+ ${chalk3.dim("Run")} ${chalk3.cyan("bragduck logout")} ${chalk3.dim("to sign out")}`,
1380
1638
  {
1381
1639
  padding: 1,
1382
1640
  margin: 1,
@@ -1386,26 +1644,20 @@ ${chalk2.dim("Run")} ${chalk2.cyan("bragduck logout")} ${chalk2.dim("to sign out
1386
1644
  )
1387
1645
  );
1388
1646
  logger.log("");
1389
- globalThis.setTimeout(() => {
1390
- process.exit(0);
1391
- }, 100);
1392
1647
  return;
1393
1648
  }
1394
1649
  }
1395
- const spinner = ora("Opening browser for authentication...").start();
1396
1650
  try {
1397
- spinner.text = "Waiting for authentication...";
1398
1651
  const userInfo = await authService.login();
1399
- spinner.succeed("Authentication successful!");
1400
1652
  logger.log("");
1401
1653
  logger.log(
1402
1654
  boxen(
1403
- `${chalk2.green.bold("\u2713 Successfully authenticated!")}
1655
+ `${chalk3.green.bold("\u2713 Successfully authenticated!")}
1404
1656
 
1405
- ${chalk2.gray("Welcome,")} ${chalk2.cyan(userInfo.name)}
1406
- ${chalk2.gray("Email:")} ${userInfo.email}
1657
+ ${chalk3.gray("Welcome,")} ${chalk3.cyan(userInfo.name)}
1658
+ ${chalk3.gray("Email:")} ${userInfo.email}
1407
1659
 
1408
- ${chalk2.dim("You can now use")} ${chalk2.cyan("bragduck scan")} ${chalk2.dim("to create brags!")}`,
1660
+ ${chalk3.dim("Use")} ${chalk3.cyan("bragduck sync")} ${chalk3.dim("to create brags")}`,
1409
1661
  {
1410
1662
  padding: 1,
1411
1663
  margin: 1,
@@ -1415,118 +1667,129 @@ ${chalk2.dim("You can now use")} ${chalk2.cyan("bragduck scan")} ${chalk2.dim("t
1415
1667
  )
1416
1668
  );
1417
1669
  logger.log("");
1418
- globalThis.setTimeout(() => {
1419
- process.exit(0);
1420
- }, 100);
1421
- return;
1422
1670
  } catch (error) {
1423
- spinner.fail("Authentication failed");
1424
- logger.log("");
1425
1671
  const err = error;
1672
+ logger.log("");
1426
1673
  logger.log(
1427
- boxen(
1428
- `${chalk2.red.bold("\u2717 Authentication Failed")}
1674
+ boxen(`${chalk3.red.bold("\u2717 Authentication Failed")}
1429
1675
 
1430
- ${err.message}
1431
-
1432
- ${chalk2.dim("Hint:")} ${getErrorHint(err)}`,
1433
- {
1434
- padding: 1,
1435
- margin: 1,
1436
- borderStyle: "round",
1437
- borderColor: "red"
1438
- }
1439
- )
1676
+ ${err.message}`, {
1677
+ padding: 1,
1678
+ margin: 1,
1679
+ borderStyle: "round",
1680
+ borderColor: "red"
1681
+ })
1440
1682
  );
1441
1683
  process.exit(1);
1442
1684
  }
1443
1685
  }
1444
- function getErrorHint(error) {
1445
- if (error.name === "OAuthError") {
1446
- if (error.message.includes("timeout")) {
1447
- return "Try again and complete the authentication within 2 minutes";
1448
- }
1449
- if (error.message.includes("CSRF")) {
1450
- return "This might be a security issue. Try running the command again";
1451
- }
1452
- return "Check your internet connection and try again";
1686
+ async function authStatus() {
1687
+ logger.log("");
1688
+ logger.info("Authentication Status:");
1689
+ logger.log("");
1690
+ const services = await storageService.getAuthenticatedServices();
1691
+ if (services.length === 0) {
1692
+ logger.info(theme.secondary("Not authenticated with any services"));
1693
+ logger.log("");
1694
+ logger.info(`Run ${theme.command("bragduck auth login")} to authenticate`);
1695
+ logger.log("");
1696
+ return;
1453
1697
  }
1454
- if (error.name === "NetworkError") {
1455
- return "Check your internet connection and firewall settings";
1698
+ const bragduckAuth = await storageService.isServiceAuthenticated("bragduck");
1699
+ if (bragduckAuth) {
1700
+ const userInfo = authService.getUserInfo();
1701
+ logger.info(`${theme.success("\u2713")} Bragduck: ${userInfo?.name || "Authenticated"}`);
1702
+ } else {
1703
+ logger.info(`${theme.error("\u2717")} Bragduck: Not authenticated`);
1456
1704
  }
1457
- if (error.name === "AuthenticationError") {
1458
- return "Verify your credentials and try again";
1705
+ for (const service of services) {
1706
+ if (service !== "bragduck") {
1707
+ logger.info(`${theme.success("\u2713")} ${service}: Authenticated`);
1708
+ }
1459
1709
  }
1460
- return "Try running the command again or check the logs with DEBUG=* bragduck init";
1710
+ logger.log("");
1461
1711
  }
1462
-
1463
- // src/commands/logout.ts
1464
- init_esm_shims();
1465
- init_auth_service();
1466
- init_logger();
1467
- import { confirm } from "@inquirer/prompts";
1468
- import boxen2 from "boxen";
1469
- import chalk3 from "chalk";
1470
- import ora2 from "ora";
1471
- async function logoutCommand() {
1472
- const isAuthenticated = await authService.isAuthenticated();
1473
- if (!isAuthenticated) {
1474
- logger.log(
1475
- boxen2(
1476
- `${chalk3.yellow("Not currently authenticated")}
1477
-
1478
- ${chalk3.dim("Nothing to logout from")}`,
1479
- {
1480
- padding: 1,
1481
- margin: 1,
1482
- borderStyle: "round",
1483
- borderColor: "yellow"
1484
- }
1485
- )
1486
- );
1487
- return;
1488
- }
1489
- const userInfo = authService.getUserInfo();
1490
- const userName = userInfo?.name || "Unknown User";
1712
+ async function authBitbucket() {
1713
+ logger.log("");
1714
+ logger.log(
1715
+ boxen(
1716
+ theme.info("Bitbucket API Token Authentication") + "\n\nCreate an API Token at:\n" + colors.highlight("https://bitbucket.org/account/settings/api-token/new") + "\n\nRequired scopes:\n \u2022 pullrequest:read\n \u2022 repository:read\n \u2022 account:read\n\n" + theme.warning("Note: API tokens expire (max 1 year)"),
1717
+ boxStyles.info
1718
+ )
1719
+ );
1491
1720
  logger.log("");
1492
- const shouldLogout = await confirm({
1493
- message: `Are you sure you want to logout? (${chalk3.cyan(userName)})`,
1494
- default: false
1495
- });
1496
- if (!shouldLogout) {
1497
- logger.info("Logout cancelled");
1498
- return;
1499
- }
1500
- const spinner = ora2("Logging out...").start();
1501
1721
  try {
1502
- await authService.logout();
1503
- spinner.succeed("Logged out successfully");
1722
+ const email = await input({
1723
+ message: "Atlassian account email:",
1724
+ validate: (value) => value.includes("@") ? true : "Please enter a valid email address"
1725
+ });
1726
+ const apiToken = await input({
1727
+ message: "API Token:",
1728
+ validate: (value) => value.length > 0 ? true : "API token cannot be empty"
1729
+ });
1730
+ const tokenExpiry = await input({
1731
+ message: "Token expiry date (YYYY-MM-DD, optional):",
1732
+ default: "",
1733
+ validate: (value) => {
1734
+ if (!value) return true;
1735
+ const date = new Date(value);
1736
+ return !isNaN(date.getTime()) ? true : "Please enter a valid date (YYYY-MM-DD)";
1737
+ }
1738
+ });
1739
+ const auth = Buffer.from(`${email}:${apiToken}`).toString("base64");
1740
+ const response = await fetch("https://api.bitbucket.org/2.0/user", {
1741
+ headers: { Authorization: `Basic ${auth}` }
1742
+ });
1743
+ if (!response.ok) {
1744
+ logger.log("");
1745
+ logger.log(
1746
+ boxen(
1747
+ theme.error("\u2717 Authentication Failed") + "\n\nInvalid email or API token",
1748
+ boxStyles.error
1749
+ )
1750
+ );
1751
+ logger.log("");
1752
+ process.exit(1);
1753
+ }
1754
+ const user = await response.json();
1755
+ const credentials = {
1756
+ accessToken: apiToken,
1757
+ username: email
1758
+ // Store email in username field
1759
+ };
1760
+ if (tokenExpiry) {
1761
+ const expiryDate = new Date(tokenExpiry);
1762
+ credentials.expiresAt = expiryDate.getTime();
1763
+ }
1764
+ await storageService.setServiceCredentials("bitbucket", credentials);
1504
1765
  logger.log("");
1505
1766
  logger.log(
1506
- boxen2(
1507
- `${chalk3.green.bold("\u2713 Logged out successfully")}
1508
-
1509
- ${chalk3.dim("Your credentials have been cleared")}
1767
+ boxen(
1768
+ theme.success("\u2713 Successfully authenticated with Bitbucket") + `
1510
1769
 
1511
- ${chalk3.dim("Run")} ${chalk3.cyan("bragduck init")} ${chalk3.dim("to login again")}`,
1512
- {
1513
- padding: 1,
1514
- margin: 1,
1515
- borderStyle: "round",
1516
- borderColor: "green"
1517
- }
1770
+ Email: ${email}
1771
+ User: ${user.display_name}
1772
+ ` + (tokenExpiry ? `Expires: ${tokenExpiry}` : ""),
1773
+ boxStyles.success
1518
1774
  )
1519
1775
  );
1520
- } catch {
1521
- spinner.fail("Logout failed");
1522
- logger.error("Failed to logout. Please try again.");
1776
+ logger.log("");
1777
+ } catch (error) {
1778
+ const err = error;
1779
+ logger.log("");
1780
+ logger.log(
1781
+ boxen(
1782
+ theme.error("\u2717 Authentication Failed") + "\n\n" + (err.message || "Unknown error"),
1783
+ boxStyles.error
1784
+ )
1785
+ );
1786
+ logger.log("");
1523
1787
  process.exit(1);
1524
1788
  }
1525
1789
  }
1526
1790
 
1527
- // src/commands/scan.ts
1791
+ // src/commands/sync.ts
1528
1792
  init_esm_shims();
1529
- import boxen6 from "boxen";
1530
1793
 
1531
1794
  // node_modules/@inquirer/core/dist/esm/lib/errors.mjs
1532
1795
  init_esm_shims();
@@ -1535,12 +1798,150 @@ var CancelPromptError = class extends Error {
1535
1798
  message = "Prompt was canceled";
1536
1799
  };
1537
1800
 
1538
- // src/services/github.service.ts
1801
+ // src/commands/sync.ts
1802
+ init_api_service();
1803
+ init_storage_service();
1804
+ init_auth_service();
1805
+ import boxen6 from "boxen";
1806
+
1807
+ // src/utils/source-detector.ts
1539
1808
  init_esm_shims();
1540
1809
  init_errors();
1541
- init_logger();
1810
+ init_storage_service();
1542
1811
  import { exec as exec2 } from "child_process";
1543
1812
  import { promisify as promisify2 } from "util";
1813
+ var execAsync2 = promisify2(exec2);
1814
+ var SourceDetector = class {
1815
+ /**
1816
+ * Detect all possible sources from git remotes
1817
+ */
1818
+ async detectSources() {
1819
+ const detected = [];
1820
+ try {
1821
+ const { stdout } = await execAsync2("git remote -v");
1822
+ const remotes = this.parseRemotes(stdout);
1823
+ for (const remote of remotes) {
1824
+ const source = this.parseRemoteUrl(remote.url);
1825
+ if (source) {
1826
+ const isAuthenticated = await this.checkAuthentication(source.type);
1827
+ detected.push({
1828
+ ...source,
1829
+ remoteUrl: remote.url,
1830
+ isAuthenticated
1831
+ });
1832
+ }
1833
+ }
1834
+ } catch {
1835
+ throw new GitError("Not a git repository");
1836
+ }
1837
+ const recommended = this.selectRecommendedSource(detected);
1838
+ return { detected, recommended };
1839
+ }
1840
+ /**
1841
+ * Parse git remote -v output
1842
+ */
1843
+ parseRemotes(output) {
1844
+ const lines = output.split("\n").filter(Boolean);
1845
+ const remotes = /* @__PURE__ */ new Map();
1846
+ for (const line of lines) {
1847
+ const match = line.match(/^(\S+)\s+(\S+)\s+\(fetch\)$/);
1848
+ if (match && match[1] && match[2]) {
1849
+ remotes.set(match[1], match[2]);
1850
+ }
1851
+ }
1852
+ return Array.from(remotes.entries()).map(([name, url]) => ({ name, url }));
1853
+ }
1854
+ /**
1855
+ * Parse remote URL to detect source type
1856
+ */
1857
+ parseRemoteUrl(url) {
1858
+ if (url.includes("github.com")) {
1859
+ const match = url.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
1860
+ if (match && match[1] && match[2]) {
1861
+ return {
1862
+ type: "github",
1863
+ host: "github.com",
1864
+ owner: match[1],
1865
+ repo: match[2]
1866
+ };
1867
+ }
1868
+ }
1869
+ if (url.includes("gitlab.com")) {
1870
+ const match = url.match(/gitlab\.com[:/]([^/]+)\/([^/.]+)/);
1871
+ if (match && match[1] && match[2]) {
1872
+ return {
1873
+ type: "gitlab",
1874
+ host: "gitlab.com",
1875
+ owner: match[1],
1876
+ repo: match[2]
1877
+ };
1878
+ }
1879
+ }
1880
+ if (url.includes("bitbucket.org")) {
1881
+ const match = url.match(/bitbucket\.org[:/]([^/]+)\/([^/.]+)/);
1882
+ if (match && match[1] && match[2]) {
1883
+ return {
1884
+ type: "bitbucket",
1885
+ host: "bitbucket.org",
1886
+ owner: match[1],
1887
+ repo: match[2]
1888
+ };
1889
+ }
1890
+ }
1891
+ if (url.match(/\/scm\/|bitbucket\./)) {
1892
+ const match = url.match(/([^/:]+)[:/]scm\/([^/]+)\/([^/.]+)/);
1893
+ if (match && match[1] && match[2] && match[3]) {
1894
+ return {
1895
+ type: "atlassian",
1896
+ host: match[1],
1897
+ owner: match[2],
1898
+ repo: match[3]
1899
+ };
1900
+ }
1901
+ }
1902
+ return null;
1903
+ }
1904
+ /**
1905
+ * Check if user is authenticated with a specific source
1906
+ */
1907
+ async checkAuthentication(type) {
1908
+ try {
1909
+ if (type === "github") {
1910
+ await execAsync2("command gh auth status");
1911
+ return true;
1912
+ } else if (type === "bitbucket" || type === "atlassian") {
1913
+ return await storageService.isServiceAuthenticated("bitbucket");
1914
+ }
1915
+ return false;
1916
+ } catch {
1917
+ return false;
1918
+ }
1919
+ }
1920
+ /**
1921
+ * Select recommended source (prefer authenticated GitHub)
1922
+ */
1923
+ selectRecommendedSource(sources) {
1924
+ const authenticated = sources.find((s) => s.isAuthenticated);
1925
+ if (authenticated) return authenticated.type;
1926
+ const github = sources.find((s) => s.type === "github");
1927
+ if (github) return "github";
1928
+ return sources[0]?.type;
1929
+ }
1930
+ };
1931
+ var sourceDetector = new SourceDetector();
1932
+
1933
+ // src/sync/adapter-factory.ts
1934
+ init_esm_shims();
1935
+
1936
+ // src/sync/github-adapter.ts
1937
+ init_esm_shims();
1938
+
1939
+ // src/services/github.service.ts
1940
+ init_esm_shims();
1941
+ init_errors();
1942
+ init_logger();
1943
+ import { exec as exec3 } from "child_process";
1944
+ import { promisify as promisify3 } from "util";
1544
1945
 
1545
1946
  // src/services/git.service.ts
1546
1947
  init_esm_shims();
@@ -1550,9 +1951,9 @@ import simpleGit from "simple-git";
1550
1951
  init_esm_shims();
1551
1952
  init_errors();
1552
1953
  import { existsSync as existsSync2 } from "fs";
1553
- import { join as join4 } from "path";
1954
+ import { join as join6 } from "path";
1554
1955
  function validateGitRepository(path2) {
1555
- const gitDir = join4(path2, ".git");
1956
+ const gitDir = join6(path2, ".git");
1556
1957
  if (!existsSync2(gitDir)) {
1557
1958
  throw new GitError(
1558
1959
  "Not a git repository. Please run this command from within a git repository.",
@@ -1765,7 +2166,7 @@ var GitService = class {
1765
2166
  var gitService = new GitService();
1766
2167
 
1767
2168
  // src/services/github.service.ts
1768
- var execAsync2 = promisify2(exec2);
2169
+ var execAsync3 = promisify3(exec3);
1769
2170
  var GitHubService = class {
1770
2171
  MAX_BODY_LENGTH = 5e3;
1771
2172
  PR_SEARCH_FIELDS = "number,title,body,author,mergedAt,additions,deletions,changedFiles,url,labels";
@@ -1774,7 +2175,7 @@ var GitHubService = class {
1774
2175
  */
1775
2176
  async checkGitHubCLI() {
1776
2177
  try {
1777
- await execAsync2("command gh --version");
2178
+ await execAsync3("command gh --version");
1778
2179
  return true;
1779
2180
  } catch {
1780
2181
  return false;
@@ -1796,7 +2197,7 @@ var GitHubService = class {
1796
2197
  */
1797
2198
  async checkAuthentication() {
1798
2199
  try {
1799
- await execAsync2("command gh auth status");
2200
+ await execAsync3("command gh auth status");
1800
2201
  return true;
1801
2202
  } catch {
1802
2203
  return false;
@@ -1821,7 +2222,7 @@ var GitHubService = class {
1821
2222
  await this.ensureGitHubCLI();
1822
2223
  await this.ensureAuthentication();
1823
2224
  await gitService.validateRepository();
1824
- const { stdout } = await execAsync2("command gh repo view --json url");
2225
+ const { stdout } = await execAsync3("command gh repo view --json url");
1825
2226
  const data = JSON.parse(stdout);
1826
2227
  if (!data.url) {
1827
2228
  throw new GitHubError("This repository is not hosted on GitHub", {
@@ -1853,7 +2254,7 @@ var GitHubService = class {
1853
2254
  async getRepositoryInfo() {
1854
2255
  try {
1855
2256
  await this.ensureGitHubCLI();
1856
- const { stdout } = await execAsync2(
2257
+ const { stdout } = await execAsync3(
1857
2258
  "command gh repo view --json owner,name,url,nameWithOwner"
1858
2259
  );
1859
2260
  const data = JSON.parse(stdout);
@@ -1878,7 +2279,7 @@ var GitHubService = class {
1878
2279
  */
1879
2280
  async getCurrentGitHubUser() {
1880
2281
  try {
1881
- const { stdout } = await execAsync2("command gh api user --jq .login");
2282
+ const { stdout } = await execAsync3("command gh api user --jq .login");
1882
2283
  return stdout.trim() || null;
1883
2284
  } catch {
1884
2285
  logger.debug("Failed to get GitHub user");
@@ -1904,7 +2305,7 @@ var GitHubService = class {
1904
2305
  const limitArg = limit ? `--limit ${limit}` : "";
1905
2306
  const command = `command gh pr list --state merged --json ${this.PR_SEARCH_FIELDS} --search "${searchQuery}" ${limitArg}`;
1906
2307
  logger.debug(`Running: ${command}`);
1907
- const { stdout } = await execAsync2(command);
2308
+ const { stdout } = await execAsync3(command);
1908
2309
  const prs = JSON.parse(stdout);
1909
2310
  logger.debug(`Found ${prs.length} merged PRs`);
1910
2311
  return prs;
@@ -1962,189 +2363,503 @@ ${truncatedBody}` : title;
1962
2363
  };
1963
2364
  var githubService = new GitHubService();
1964
2365
 
1965
- // src/commands/scan.ts
1966
- init_api_service();
1967
- init_storage_service();
1968
- init_logger();
1969
- init_browser();
1970
-
1971
- // src/utils/auth-helper.ts
1972
- init_esm_shims();
1973
- init_auth_service();
1974
- import boxen5 from "boxen";
1975
- init_logger();
1976
-
1977
- // src/ui/prompts.ts
1978
- init_esm_shims();
1979
- import { checkbox, confirm as confirm2, input, select, editor } from "@inquirer/prompts";
1980
- import boxen4 from "boxen";
2366
+ // src/sync/github-adapter.ts
2367
+ var GitHubSyncAdapter = class {
2368
+ name = "github";
2369
+ async validate() {
2370
+ await githubService.validateGitHubRepository();
2371
+ }
2372
+ async getRepositoryInfo() {
2373
+ const info = await githubService.getRepositoryInfo();
2374
+ return {
2375
+ owner: info.owner,
2376
+ name: info.name,
2377
+ fullName: info.fullName,
2378
+ url: info.url
2379
+ };
2380
+ }
2381
+ async fetchWorkItems(options) {
2382
+ let prs;
2383
+ if (options.author) {
2384
+ prs = await githubService.getMergedPRs({
2385
+ days: options.days,
2386
+ limit: options.limit,
2387
+ author: options.author
2388
+ });
2389
+ } else {
2390
+ prs = await githubService.getPRsByCurrentUser({
2391
+ days: options.days,
2392
+ limit: options.limit
2393
+ });
2394
+ }
2395
+ return prs.map((pr) => githubService.transformPRToCommit(pr));
2396
+ }
2397
+ async isAuthenticated() {
2398
+ try {
2399
+ await githubService.validateGitHubRepository();
2400
+ return true;
2401
+ } catch {
2402
+ return false;
2403
+ }
2404
+ }
2405
+ async getCurrentUser() {
2406
+ return githubService.getCurrentGitHubUser();
2407
+ }
2408
+ };
2409
+ var githubSyncAdapter = new GitHubSyncAdapter();
1981
2410
 
1982
- // src/ui/formatters.ts
2411
+ // src/sync/bitbucket-adapter.ts
1983
2412
  init_esm_shims();
1984
- import Table from "cli-table3";
1985
- import terminalLink from "terminal-link";
1986
2413
 
1987
- // src/ui/theme.ts
2414
+ // src/services/bitbucket.service.ts
1988
2415
  init_esm_shims();
1989
- import chalk5 from "chalk";
1990
- var colors = {
1991
- // Primary colors for main actions and interactive elements
1992
- primary: chalk5.cyan,
1993
- // Success states
1994
- success: chalk5.green,
1995
- successBold: chalk5.green.bold,
1996
- // Warning states
1997
- warning: chalk5.yellow,
1998
- warningBold: chalk5.yellow.bold,
1999
- // Error states
2000
- error: chalk5.red,
2001
- errorBold: chalk5.red.bold,
2002
- // Info and metadata
2003
- info: chalk5.gray,
2004
- infoDim: chalk5.dim,
2005
- // Highlighted/important data
2006
- highlight: chalk5.yellow.bold,
2007
- highlightCyan: chalk5.cyan.bold,
2008
- // Text emphasis
2009
- bold: chalk5.bold,
2010
- dim: chalk5.dim,
2011
- // Special purpose
2012
- white: chalk5.white,
2013
- gray: chalk5.gray,
2014
- // Links and URLs
2015
- link: chalk5.blue.underline
2016
- };
2017
- var theme = {
2018
- /**
2019
- * Format command names and CLI actions
2020
- */
2021
- command: (text) => colors.primary(text),
2416
+ init_errors();
2417
+ init_logger();
2418
+ init_storage_service();
2419
+ import { exec as exec4 } from "child_process";
2420
+ import { promisify as promisify4 } from "util";
2421
+ var execAsync4 = promisify4(exec4);
2422
+ var BitbucketService = class {
2423
+ BITBUCKET_API_BASE = "https://api.bitbucket.org/2.0";
2424
+ MAX_DESCRIPTION_LENGTH = 5e3;
2022
2425
  /**
2023
- * Format success messages
2426
+ * Get stored Bitbucket credentials
2024
2427
  */
2025
- success: (text) => colors.success(`\u2713 ${text}`),
2026
- successBold: (text) => colors.successBold(`\u2713 ${text}`),
2428
+ async getCredentials() {
2429
+ const creds = await storageService.getServiceCredentials("bitbucket");
2430
+ if (!creds || !creds.username || !creds.accessToken) {
2431
+ throw new BitbucketError("Not authenticated with Bitbucket", {
2432
+ hint: "Run: bragduck auth bitbucket"
2433
+ });
2434
+ }
2435
+ if (creds.expiresAt && creds.expiresAt < Date.now()) {
2436
+ throw new BitbucketError("API token has expired", {
2437
+ hint: "Run: bragduck auth bitbucket"
2438
+ });
2439
+ }
2440
+ return {
2441
+ email: creds.username,
2442
+ // username field stores email
2443
+ apiToken: creds.accessToken
2444
+ };
2445
+ }
2027
2446
  /**
2028
- * Format error messages
2447
+ * Make authenticated request to Bitbucket API
2029
2448
  */
2030
- error: (text) => colors.error(`\u2717 ${text}`),
2031
- errorBold: (text) => colors.errorBold(`\u2717 ${text}`),
2449
+ async request(endpoint) {
2450
+ const { email, apiToken } = await this.getCredentials();
2451
+ const auth = Buffer.from(`${email}:${apiToken}`).toString("base64");
2452
+ logger.debug(`Bitbucket API: GET ${endpoint}`);
2453
+ const response = await fetch(`${this.BITBUCKET_API_BASE}${endpoint}`, {
2454
+ headers: {
2455
+ Authorization: `Basic ${auth}`,
2456
+ Accept: "application/json"
2457
+ }
2458
+ });
2459
+ if (!response.ok) {
2460
+ const statusText = response.statusText;
2461
+ const status = response.status;
2462
+ if (status === 401) {
2463
+ throw new BitbucketError("Invalid or expired API token", {
2464
+ hint: "Run: bragduck auth bitbucket",
2465
+ originalError: statusText
2466
+ });
2467
+ } else if (status === 403) {
2468
+ throw new BitbucketError("Forbidden - check token permissions", {
2469
+ hint: "Token needs: pullrequest:read, repository:read, account:read",
2470
+ originalError: statusText
2471
+ });
2472
+ } else if (status === 404) {
2473
+ throw new BitbucketError("Repository or resource not found", {
2474
+ originalError: statusText
2475
+ });
2476
+ } else if (status === 429) {
2477
+ throw new BitbucketError("Rate limit exceeded", {
2478
+ hint: "Wait a few minutes before trying again",
2479
+ originalError: statusText
2480
+ });
2481
+ }
2482
+ throw new BitbucketError(`API request failed: ${statusText}`, {
2483
+ originalError: statusText
2484
+ });
2485
+ }
2486
+ return response.json();
2487
+ }
2032
2488
  /**
2033
- * Format warning messages
2489
+ * Extract workspace and repo from git remote URL
2034
2490
  */
2035
- warning: (text) => colors.warning(`\u26A0 ${text}`),
2491
+ parseRemoteUrl(url) {
2492
+ const match = url.match(/bitbucket\.org[:/]([^/]+)\/([^/.]+)/);
2493
+ if (match && match[1] && match[2]) {
2494
+ return {
2495
+ workspace: match[1],
2496
+ repo: match[2].replace(/\.git$/, "")
2497
+ };
2498
+ }
2499
+ return null;
2500
+ }
2036
2501
  /**
2037
- * Format info messages
2502
+ * Get repository info from git remote
2038
2503
  */
2039
- info: (text) => colors.info(text),
2504
+ async getRepoFromGit() {
2505
+ try {
2506
+ const { stdout } = await execAsync4("command git remote get-url origin");
2507
+ const remoteUrl = stdout.trim();
2508
+ const parsed = this.parseRemoteUrl(remoteUrl);
2509
+ if (!parsed) {
2510
+ throw new BitbucketError("Could not parse Bitbucket repository from git remote", {
2511
+ hint: "Ensure this is a Bitbucket repository"
2512
+ });
2513
+ }
2514
+ return parsed;
2515
+ } catch (error) {
2516
+ if (error instanceof BitbucketError) {
2517
+ throw error;
2518
+ }
2519
+ throw new GitError("Could not get git remote URL");
2520
+ }
2521
+ }
2040
2522
  /**
2041
- * Format labels (e.g., "User:", "Email:")
2523
+ * Validate that this is a Bitbucket repository and credentials work
2042
2524
  */
2043
- label: (text) => colors.gray(`${text}:`),
2525
+ async validateBitbucketRepository() {
2526
+ await gitService.validateRepository();
2527
+ const { workspace, repo } = await this.getRepoFromGit();
2528
+ try {
2529
+ await this.request(`/repositories/${workspace}/${repo}`);
2530
+ } catch (error) {
2531
+ if (error instanceof BitbucketError) {
2532
+ throw error;
2533
+ }
2534
+ throw new BitbucketError("Could not access Bitbucket repository via API", {
2535
+ hint: "Check that this is a Bitbucket repository and your credentials are valid",
2536
+ originalError: error instanceof Error ? error.message : String(error)
2537
+ });
2538
+ }
2539
+ }
2044
2540
  /**
2045
- * Format values
2541
+ * Get repository information
2046
2542
  */
2047
- value: (text) => colors.highlight(text),
2543
+ async getRepositoryInfo() {
2544
+ const { workspace, repo } = await this.getRepoFromGit();
2545
+ const bitbucketRepo = await this.request(
2546
+ `/repositories/${workspace}/${repo}`
2547
+ );
2548
+ return {
2549
+ owner: workspace,
2550
+ name: bitbucketRepo.name,
2551
+ fullName: bitbucketRepo.full_name,
2552
+ url: bitbucketRepo.links.html.href
2553
+ };
2554
+ }
2048
2555
  /**
2049
- * Format counts and numbers
2556
+ * Get current user's account ID
2050
2557
  */
2051
- count: (num) => colors.highlightCyan(num.toString()),
2558
+ async getCurrentUserAccountId() {
2559
+ const user = await this.request("/user");
2560
+ return user.account_id;
2561
+ }
2052
2562
  /**
2053
- * Format file paths
2563
+ * Get current user's username (nickname)
2054
2564
  */
2055
- path: (text) => colors.info(text),
2565
+ async getCurrentGitHubUser() {
2566
+ try {
2567
+ const user = await this.request("/user");
2568
+ return user.nickname;
2569
+ } catch {
2570
+ return null;
2571
+ }
2572
+ }
2056
2573
  /**
2057
- * Format step indicators (e.g., "Step 1/5:")
2574
+ * Fetch merged pull requests with optional filtering
2058
2575
  */
2059
- step: (current, total) => colors.primary(`[${current}/${total}]`),
2060
- /**
2061
- * Format progress text
2062
- */
2063
- progress: (text) => colors.infoDim(text),
2576
+ async getMergedPRs(options = {}) {
2577
+ const { workspace, repo } = await this.getRepoFromGit();
2578
+ const queries = ['state="MERGED"'];
2579
+ if (options.days) {
2580
+ const since = /* @__PURE__ */ new Date();
2581
+ since.setDate(since.getDate() - options.days);
2582
+ queries.push(`updated_on>=${since.toISOString()}`);
2583
+ }
2584
+ if (options.author) {
2585
+ queries.push(`author.account_id="${options.author}"`);
2586
+ }
2587
+ const queryString = queries.join(" AND ");
2588
+ const allPRs = [];
2589
+ let endpoint = `/repositories/${workspace}/${repo}/pullrequests?q=${encodeURIComponent(queryString)}&pagelen=100`;
2590
+ while (endpoint) {
2591
+ const response = await this.request(endpoint);
2592
+ allPRs.push(...response.values);
2593
+ logger.debug(
2594
+ `Fetched ${response.values.length} PRs (total: ${allPRs.length})${response.next ? ", fetching next page..." : ""}`
2595
+ );
2596
+ if (options.limit && allPRs.length >= options.limit) {
2597
+ return allPRs.slice(0, options.limit);
2598
+ }
2599
+ if (response.next) {
2600
+ const url = new URL(response.next);
2601
+ endpoint = url.pathname + url.search;
2602
+ } else {
2603
+ endpoint = "";
2604
+ }
2605
+ }
2606
+ return allPRs;
2607
+ }
2064
2608
  /**
2065
- * Format emphasized text
2609
+ * Fetch PRs for the current authenticated user
2066
2610
  */
2067
- emphasis: (text) => colors.bold(text),
2611
+ async getPRsByCurrentUser(options = {}) {
2612
+ const accountId = await this.getCurrentUserAccountId();
2613
+ return this.getMergedPRs({
2614
+ ...options,
2615
+ author: accountId
2616
+ });
2617
+ }
2068
2618
  /**
2069
- * Format de-emphasized/secondary text
2619
+ * Transform Bitbucket PR to GitCommit format
2070
2620
  */
2071
- secondary: (text) => colors.dim(text),
2621
+ transformPRToCommit(pr) {
2622
+ let message = pr.title;
2623
+ if (pr.description) {
2624
+ const truncatedDesc = pr.description.substring(0, this.MAX_DESCRIPTION_LENGTH);
2625
+ message = `${pr.title}
2626
+
2627
+ ${truncatedDesc}`;
2628
+ }
2629
+ return {
2630
+ sha: `pr-${pr.id}`,
2631
+ message,
2632
+ author: pr.author.nickname,
2633
+ authorEmail: "",
2634
+ // Not available in Bitbucket API
2635
+ date: pr.created_on,
2636
+ url: pr.links.html.href,
2637
+ diffStats: {
2638
+ filesChanged: 0,
2639
+ // Would require separate API call to diffstat endpoint
2640
+ insertions: 0,
2641
+ deletions: 0
2642
+ }
2643
+ };
2644
+ }
2645
+ };
2646
+ var bitbucketService = new BitbucketService();
2647
+
2648
+ // src/sync/bitbucket-adapter.ts
2649
+ var BitbucketSyncAdapter = class {
2650
+ name = "bitbucket";
2651
+ async validate() {
2652
+ await bitbucketService.validateBitbucketRepository();
2653
+ }
2654
+ async getRepositoryInfo() {
2655
+ const info = await bitbucketService.getRepositoryInfo();
2656
+ return {
2657
+ owner: info.owner,
2658
+ name: info.name,
2659
+ fullName: info.fullName,
2660
+ url: info.url
2661
+ };
2662
+ }
2663
+ async fetchWorkItems(options) {
2664
+ let prs;
2665
+ if (options.author) {
2666
+ prs = await bitbucketService.getMergedPRs({
2667
+ days: options.days,
2668
+ limit: options.limit,
2669
+ author: options.author
2670
+ });
2671
+ } else {
2672
+ prs = await bitbucketService.getPRsByCurrentUser({
2673
+ days: options.days,
2674
+ limit: options.limit
2675
+ });
2676
+ }
2677
+ return prs.map((pr) => bitbucketService.transformPRToCommit(pr));
2678
+ }
2679
+ async isAuthenticated() {
2680
+ try {
2681
+ await bitbucketService.validateBitbucketRepository();
2682
+ return true;
2683
+ } catch {
2684
+ return false;
2685
+ }
2686
+ }
2687
+ async getCurrentUser() {
2688
+ return bitbucketService.getCurrentGitHubUser();
2689
+ }
2690
+ };
2691
+ var bitbucketSyncAdapter = new BitbucketSyncAdapter();
2692
+
2693
+ // src/sync/adapter-factory.ts
2694
+ var AdapterFactory = class {
2072
2695
  /**
2073
- * Format interactive elements (prompts, selections)
2696
+ * Get adapter for a specific source type
2074
2697
  */
2075
- interactive: (text) => colors.primary(text),
2698
+ static getAdapter(source) {
2699
+ switch (source) {
2700
+ case "github":
2701
+ return githubSyncAdapter;
2702
+ case "bitbucket":
2703
+ case "atlassian":
2704
+ return bitbucketSyncAdapter;
2705
+ // Bitbucket Cloud and Server use same adapter
2706
+ case "gitlab":
2707
+ throw new Error(`${source} adapter not yet implemented`);
2708
+ default:
2709
+ throw new Error(`Unknown source type: ${source}`);
2710
+ }
2711
+ }
2076
2712
  /**
2077
- * Format keys in key-value pairs
2713
+ * Check if adapter is available for source
2078
2714
  */
2079
- key: (text) => colors.white(text)
2080
- };
2081
- var boxStyles = {
2082
- success: {
2083
- borderColor: "green",
2084
- borderStyle: "round",
2085
- padding: 1,
2086
- margin: 1
2087
- },
2088
- error: {
2089
- borderColor: "red",
2090
- borderStyle: "round",
2091
- padding: 1,
2092
- margin: 1
2093
- },
2094
- warning: {
2095
- borderColor: "yellow",
2096
- borderStyle: "round",
2097
- padding: 1,
2098
- margin: 1
2099
- },
2100
- info: {
2101
- borderColor: "cyan",
2102
- borderStyle: "round",
2103
- padding: 1,
2104
- margin: 1
2105
- },
2106
- plain: {
2107
- borderColor: "gray",
2108
- borderStyle: "round",
2109
- padding: 1,
2110
- margin: 1
2715
+ static isSupported(source) {
2716
+ return source === "github" || source === "bitbucket" || source === "atlassian";
2111
2717
  }
2112
2718
  };
2113
- var tableStyles = {
2114
- default: {
2115
- style: {
2116
- head: [],
2117
- border: ["gray"]
2118
- }
2119
- },
2120
- compact: {
2121
- style: {
2122
- head: [],
2123
- border: ["dim"],
2124
- compact: true
2719
+
2720
+ // src/commands/sync.ts
2721
+ init_logger();
2722
+
2723
+ // src/utils/auth-helper.ts
2724
+ init_esm_shims();
2725
+ init_auth_service();
2726
+ import boxen5 from "boxen";
2727
+
2728
+ // src/commands/init.ts
2729
+ init_esm_shims();
2730
+ init_auth_service();
2731
+ init_logger();
2732
+ import ora from "ora";
2733
+ import boxen3 from "boxen";
2734
+ import chalk5 from "chalk";
2735
+ async function initCommand() {
2736
+ logger.log("");
2737
+ logger.log(
2738
+ boxen3(
2739
+ chalk5.yellow.bold("\u26A0 Deprecation Notice") + `
2740
+
2741
+ The ${chalk5.cyan("init")} command is deprecated.
2742
+ Please use ${chalk5.cyan("bragduck auth login")} instead.
2743
+
2744
+ ` + chalk5.dim("This command will be removed in v3.0.0"),
2745
+ {
2746
+ padding: 1,
2747
+ borderStyle: "round",
2748
+ borderColor: "yellow",
2749
+ dimBorder: true
2750
+ }
2751
+ )
2752
+ );
2753
+ logger.log("");
2754
+ logger.info("Starting authentication flow...");
2755
+ logger.log("");
2756
+ const isAuthenticated = await authService.isAuthenticated();
2757
+ if (isAuthenticated) {
2758
+ const userInfo = authService.getUserInfo();
2759
+ if (userInfo) {
2760
+ logger.log(
2761
+ boxen3(
2762
+ `${chalk5.yellow("Already authenticated!")}
2763
+
2764
+ ${chalk5.gray("User:")} ${userInfo.name}
2765
+ ${chalk5.gray("Email:")} ${userInfo.email}
2766
+
2767
+ ${chalk5.dim("Run")} ${chalk5.cyan("bragduck logout")} ${chalk5.dim("to sign out")}`,
2768
+ {
2769
+ padding: 1,
2770
+ margin: 1,
2771
+ borderStyle: "round",
2772
+ borderColor: "yellow"
2773
+ }
2774
+ )
2775
+ );
2776
+ logger.log("");
2777
+ globalThis.setTimeout(() => {
2778
+ process.exit(0);
2779
+ }, 100);
2780
+ return;
2125
2781
  }
2126
2782
  }
2127
- };
2128
- var sizeIndicators = {
2129
- small: colors.success("\u25CF"),
2130
- medium: colors.warning("\u25CF"),
2131
- large: colors.error("\u25CF")
2132
- };
2133
- function getSizeIndicator(insertions, deletions) {
2134
- const total = insertions + deletions;
2135
- if (total < 50) {
2136
- return `${sizeIndicators.small} Small`;
2137
- } else if (total < 200) {
2138
- return `${sizeIndicators.medium} Medium`;
2139
- } else {
2140
- return `${sizeIndicators.large} Large`;
2783
+ const spinner = ora("Opening browser for authentication...").start();
2784
+ try {
2785
+ spinner.text = "Waiting for authentication...";
2786
+ const userInfo = await authService.login();
2787
+ spinner.succeed("Authentication successful!");
2788
+ logger.log("");
2789
+ logger.log(
2790
+ boxen3(
2791
+ `${chalk5.green.bold("\u2713 Successfully authenticated!")}
2792
+
2793
+ ${chalk5.gray("Welcome,")} ${chalk5.cyan(userInfo.name)}
2794
+ ${chalk5.gray("Email:")} ${userInfo.email}
2795
+
2796
+ ${chalk5.dim("You can now use")} ${chalk5.cyan("bragduck scan")} ${chalk5.dim("to create brags!")}`,
2797
+ {
2798
+ padding: 1,
2799
+ margin: 1,
2800
+ borderStyle: "round",
2801
+ borderColor: "green"
2802
+ }
2803
+ )
2804
+ );
2805
+ logger.log("");
2806
+ globalThis.setTimeout(() => {
2807
+ process.exit(0);
2808
+ }, 100);
2809
+ return;
2810
+ } catch (error) {
2811
+ spinner.fail("Authentication failed");
2812
+ logger.log("");
2813
+ const err = error;
2814
+ logger.log(
2815
+ boxen3(
2816
+ `${chalk5.red.bold("\u2717 Authentication Failed")}
2817
+
2818
+ ${err.message}
2819
+
2820
+ ${chalk5.dim("Hint:")} ${getErrorHint(err)}`,
2821
+ {
2822
+ padding: 1,
2823
+ margin: 1,
2824
+ borderStyle: "round",
2825
+ borderColor: "red"
2826
+ }
2827
+ )
2828
+ );
2829
+ process.exit(1);
2141
2830
  }
2142
2831
  }
2143
- function formatDiffStats(insertions, deletions) {
2144
- return `${colors.success(`+${insertions}`)} ${colors.error(`-${deletions}`)}`;
2832
+ function getErrorHint(error) {
2833
+ if (error.name === "OAuthError") {
2834
+ if (error.message.includes("timeout")) {
2835
+ return "Try again and complete the authentication within 2 minutes";
2836
+ }
2837
+ if (error.message.includes("CSRF")) {
2838
+ return "This might be a security issue. Try running the command again";
2839
+ }
2840
+ return "Check your internet connection and try again";
2841
+ }
2842
+ if (error.name === "NetworkError") {
2843
+ return "Check your internet connection and firewall settings";
2844
+ }
2845
+ if (error.name === "AuthenticationError") {
2846
+ return "Verify your credentials and try again";
2847
+ }
2848
+ return "Try running the command again or check the logs with DEBUG=* bragduck init";
2145
2849
  }
2146
2850
 
2851
+ // src/utils/auth-helper.ts
2852
+ init_logger();
2853
+
2854
+ // src/ui/prompts.ts
2855
+ init_esm_shims();
2856
+ import { checkbox, confirm, input as input2, select, editor } from "@inquirer/prompts";
2857
+ import boxen4 from "boxen";
2858
+
2147
2859
  // src/ui/formatters.ts
2860
+ init_esm_shims();
2861
+ import Table from "cli-table3";
2862
+ import terminalLink from "terminal-link";
2148
2863
  function formatCommitChoice(commit) {
2149
2864
  let displaySha;
2150
2865
  if (commit.sha.startsWith("pr-")) {
@@ -2281,7 +2996,7 @@ async function promptSelectCommits(commits) {
2281
2996
  return selected;
2282
2997
  }
2283
2998
  async function promptConfirm(message, defaultValue = true) {
2284
- return await confirm2({
2999
+ return await confirm({
2285
3000
  message,
2286
3001
  default: defaultValue
2287
3002
  });
@@ -2300,7 +3015,7 @@ async function promptDaysToScan(defaultDays = 30) {
2300
3015
  default: "30"
2301
3016
  });
2302
3017
  if (selected === "custom") {
2303
- const customDays = await input({
3018
+ const customDays = await input2({
2304
3019
  message: "Enter number of days:",
2305
3020
  default: defaultDays.toString(),
2306
3021
  validate: (value) => {
@@ -2420,123 +3135,465 @@ ${theme.label("PR Link")} ${colors.link(prUrl)}`;
2420
3135
  for (let j = i + 1; j < refinedBrags.length; j++) {
2421
3136
  acceptedBrags.push(refinedBrags[j]);
2422
3137
  }
2423
- reviewingBrag = false;
2424
- i = refinedBrags.length - 1;
2425
- break;
2426
- }
2427
- if (action === "skip") {
2428
- reviewingBrag = false;
2429
- continue;
2430
- }
2431
- if (action === "accept") {
2432
- acceptedBrags.push(currentBrag);
2433
- reviewingBrag = false;
2434
- continue;
2435
- }
2436
- let editedBrag = { ...currentBrag };
2437
- if (action === "edit-title" || action === "edit-both") {
2438
- console.log("");
2439
- const newTitle = await input({
2440
- message: "Enter new title:",
2441
- default: currentBrag.refined_title
2442
- });
2443
- editedBrag.refined_title = newTitle;
2444
- }
2445
- if (action === "edit-desc" || action === "edit-both") {
2446
- console.log("");
2447
- const newDesc = await editor({
2448
- message: "Edit description (will open your default editor):",
2449
- default: currentBrag.refined_description
2450
- });
2451
- editedBrag.refined_description = newDesc;
3138
+ reviewingBrag = false;
3139
+ i = refinedBrags.length - 1;
3140
+ break;
3141
+ }
3142
+ if (action === "skip") {
3143
+ reviewingBrag = false;
3144
+ continue;
3145
+ }
3146
+ if (action === "accept") {
3147
+ acceptedBrags.push(currentBrag);
3148
+ reviewingBrag = false;
3149
+ continue;
3150
+ }
3151
+ let editedBrag = { ...currentBrag };
3152
+ if (action === "edit-title" || action === "edit-both") {
3153
+ console.log("");
3154
+ const newTitle = await input2({
3155
+ message: "Enter new title:",
3156
+ default: currentBrag.refined_title
3157
+ });
3158
+ editedBrag.refined_title = newTitle;
3159
+ }
3160
+ if (action === "edit-desc" || action === "edit-both") {
3161
+ console.log("");
3162
+ const newDesc = await editor({
3163
+ message: "Edit description (will open your default editor):",
3164
+ default: currentBrag.refined_description
3165
+ });
3166
+ editedBrag.refined_description = newDesc;
3167
+ }
3168
+ currentBrag = editedBrag;
3169
+ console.log("\n" + theme.success("\u2713 Changes saved. Review your edits:") + "\n");
3170
+ }
3171
+ }
3172
+ return acceptedBrags;
3173
+ }
3174
+
3175
+ // src/utils/auth-helper.ts
3176
+ async function ensureAuthenticated() {
3177
+ const isAuthenticated = await authService.isAuthenticated();
3178
+ if (isAuthenticated) {
3179
+ return true;
3180
+ }
3181
+ logger.log("");
3182
+ logger.log(
3183
+ boxen5(
3184
+ theme.warning("Not authenticated") + "\n\nYou need to be logged in to use this command.\n\nWould you like to authenticate now?",
3185
+ boxStyles.warning
3186
+ )
3187
+ );
3188
+ logger.log("");
3189
+ const shouldAuth = await promptConfirm("Authenticate now?", true);
3190
+ if (!shouldAuth) {
3191
+ logger.log("");
3192
+ logger.info(
3193
+ theme.secondary("Authentication skipped. Run ") + theme.command("bragduck init") + theme.secondary(" when you're ready to authenticate.")
3194
+ );
3195
+ logger.log("");
3196
+ return false;
3197
+ }
3198
+ try {
3199
+ await initCommand();
3200
+ return true;
3201
+ } catch {
3202
+ return false;
3203
+ }
3204
+ }
3205
+
3206
+ // src/ui/spinners.ts
3207
+ init_esm_shims();
3208
+ import ora2 from "ora";
3209
+ function createSpinner(text) {
3210
+ return ora2({
3211
+ text,
3212
+ color: "cyan",
3213
+ spinner: "dots"
3214
+ });
3215
+ }
3216
+ function createStepSpinner(currentStep, totalSteps, text) {
3217
+ const stepIndicator = theme.step(currentStep, totalSteps);
3218
+ return ora2({
3219
+ text: `${stepIndicator} ${text}`,
3220
+ color: "cyan",
3221
+ spinner: "dots"
3222
+ });
3223
+ }
3224
+ function fetchingBragsSpinner() {
3225
+ return createSpinner("Fetching your brags...");
3226
+ }
3227
+ function succeedSpinner(spinner, text) {
3228
+ if (text) {
3229
+ spinner.succeed(colors.success(text));
3230
+ } else {
3231
+ spinner.succeed();
3232
+ }
3233
+ }
3234
+ function failSpinner(spinner, text) {
3235
+ if (text) {
3236
+ spinner.fail(colors.error(text));
3237
+ } else {
3238
+ spinner.fail();
3239
+ }
3240
+ }
3241
+ function succeedStepSpinner(spinner, currentStep, totalSteps, text) {
3242
+ const stepIndicator = theme.step(currentStep, totalSteps);
3243
+ spinner.succeed(`${stepIndicator} ${colors.success(text)}`);
3244
+ }
3245
+ function failStepSpinner(spinner, currentStep, totalSteps, text) {
3246
+ const stepIndicator = theme.step(currentStep, totalSteps);
3247
+ spinner.fail(`${stepIndicator} ${colors.error(text)}`);
3248
+ }
3249
+
3250
+ // src/commands/sync.ts
3251
+ async function syncCommand(options = {}) {
3252
+ logger.log("");
3253
+ const TOTAL_STEPS = 5;
3254
+ try {
3255
+ const isAuthenticated = await ensureAuthenticated();
3256
+ if (!isAuthenticated) {
3257
+ process.exit(1);
3258
+ }
3259
+ logger.debug("Fetching subscription status...");
3260
+ const subscriptionStatus = await apiService.getSubscriptionStatus();
3261
+ logger.debug("Subscription status response:", JSON.stringify(subscriptionStatus, null, 2));
3262
+ if (subscriptionStatus.tier === "FREE") {
3263
+ logger.debug("FREE tier detected - blocking sync command");
3264
+ logger.log("");
3265
+ logger.log(
3266
+ boxen6(
3267
+ 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"),
3268
+ {
3269
+ ...boxStyles.warning,
3270
+ padding: 1,
3271
+ margin: 1
3272
+ }
3273
+ )
3274
+ );
3275
+ logger.log("");
3276
+ return;
3277
+ }
3278
+ logger.debug(`Subscription tier "${subscriptionStatus.tier}" - proceeding with sync`);
3279
+ const detectionSpinner = createStepSpinner(1, TOTAL_STEPS, "Detecting repository source");
3280
+ detectionSpinner.start();
3281
+ const detectionResult = await sourceDetector.detectSources();
3282
+ if (detectionResult.detected.length === 0) {
3283
+ failStepSpinner(detectionSpinner, 1, TOTAL_STEPS, "No supported sources detected");
3284
+ logger.log("");
3285
+ logger.info("Make sure you are in a git repository with a remote URL");
3286
+ return;
3287
+ }
3288
+ const sourceType = options.source || detectionResult.recommended;
3289
+ if (!sourceType) {
3290
+ failStepSpinner(detectionSpinner, 1, TOTAL_STEPS, "Could not determine source");
3291
+ return;
3292
+ }
3293
+ if (!AdapterFactory.isSupported(sourceType)) {
3294
+ failStepSpinner(detectionSpinner, 1, TOTAL_STEPS, `Source ${sourceType} not yet supported`);
3295
+ logger.log("");
3296
+ logger.info(`Currently supported: GitHub`);
3297
+ logger.info(`Coming soon: GitLab, Atlassian, Bitbucket`);
3298
+ return;
3299
+ }
3300
+ succeedStepSpinner(detectionSpinner, 1, TOTAL_STEPS, `Source: ${theme.value(sourceType)}`);
3301
+ logger.log("");
3302
+ const adapter = AdapterFactory.getAdapter(sourceType);
3303
+ const repoSpinner = createStepSpinner(2, TOTAL_STEPS, "Validating repository");
3304
+ repoSpinner.start();
3305
+ await adapter.validate();
3306
+ const repoInfo = await adapter.getRepositoryInfo();
3307
+ succeedStepSpinner(
3308
+ repoSpinner,
3309
+ 2,
3310
+ TOTAL_STEPS,
3311
+ `Repository: ${theme.value(repoInfo.fullName)}`
3312
+ );
3313
+ logger.log("");
3314
+ let days = options.days;
3315
+ if (!days) {
3316
+ const defaultDays = storageService.getConfig("defaultCommitDays");
3317
+ days = await promptDaysToScan(defaultDays);
3318
+ logger.log("");
3319
+ }
3320
+ const fetchSpinner = createStepSpinner(
3321
+ 3,
3322
+ TOTAL_STEPS,
3323
+ `Fetching work items from the last ${days} days`
3324
+ );
3325
+ fetchSpinner.start();
3326
+ const workItems = await adapter.fetchWorkItems({
3327
+ days,
3328
+ author: options.all ? void 0 : await adapter.getCurrentUser() || void 0
3329
+ });
3330
+ if (workItems.length === 0) {
3331
+ failStepSpinner(fetchSpinner, 3, TOTAL_STEPS, `No work items found in the last ${days} days`);
3332
+ logger.log("");
3333
+ logger.info("Try increasing the number of days or check your activity");
3334
+ return;
3335
+ }
3336
+ succeedStepSpinner(
3337
+ fetchSpinner,
3338
+ 3,
3339
+ TOTAL_STEPS,
3340
+ `Found ${theme.count(workItems.length)} work item${workItems.length > 1 ? "s" : ""}`
3341
+ );
3342
+ logger.log("");
3343
+ logger.log(formatCommitStats(workItems));
3344
+ logger.log("");
3345
+ let sortedCommits = [...workItems];
3346
+ if (workItems.length > 1) {
3347
+ const sortOption = await promptSortOption();
3348
+ logger.log("");
3349
+ if (sortOption === "date") {
3350
+ sortedCommits.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
3351
+ } else if (sortOption === "size") {
3352
+ sortedCommits.sort((a, b) => {
3353
+ const sizeA = (a.diffStats?.insertions || 0) + (a.diffStats?.deletions || 0);
3354
+ const sizeB = (b.diffStats?.insertions || 0) + (b.diffStats?.deletions || 0);
3355
+ return sizeB - sizeA;
3356
+ });
3357
+ } else if (sortOption === "files") {
3358
+ sortedCommits.sort((a, b) => {
3359
+ const filesA = a.diffStats?.filesChanged || 0;
3360
+ const filesB = b.diffStats?.filesChanged || 0;
3361
+ return filesB - filesA;
3362
+ });
3363
+ }
3364
+ }
3365
+ const selectedShas = await promptSelectCommits(sortedCommits);
3366
+ if (selectedShas.length === 0) {
3367
+ logger.log("");
3368
+ logger.info(theme.secondary("No work items selected. Sync cancelled."));
3369
+ logger.log("");
3370
+ return;
3371
+ }
3372
+ const selectedCommits = sortedCommits.filter((c) => selectedShas.includes(c.sha));
3373
+ logger.log(formatSelectionSummary(selectedCommits.length, selectedCommits));
3374
+ logger.log("");
3375
+ const existingBrags = await apiService.listBrags({ limit: 100 });
3376
+ logger.debug(`Fetched ${existingBrags.brags.length} existing brags`);
3377
+ const existingUrls = new Set(existingBrags.brags.flatMap((b) => b.attachments || []));
3378
+ logger.debug(`Existing URLs in attachments: ${existingUrls.size}`);
3379
+ const duplicates = selectedCommits.filter((c) => c.url && existingUrls.has(c.url));
3380
+ const newCommits = selectedCommits.filter((c) => !c.url || !existingUrls.has(c.url));
3381
+ logger.debug(`Duplicates: ${duplicates.length}, New: ${newCommits.length}`);
3382
+ if (duplicates.length > 0) {
3383
+ logger.log("");
3384
+ logger.info(
3385
+ colors.warning(
3386
+ `${duplicates.length} work item${duplicates.length > 1 ? "s" : ""} already added to Bragduck - skipping`
3387
+ )
3388
+ );
3389
+ logger.log("");
3390
+ }
3391
+ if (newCommits.length === 0) {
3392
+ logger.log("");
3393
+ logger.info(
3394
+ theme.secondary("All selected work items already exist in Bragduck. Nothing to refine.")
3395
+ );
3396
+ logger.log("");
3397
+ return;
3398
+ }
3399
+ const refineSpinner = createStepSpinner(
3400
+ 4,
3401
+ TOTAL_STEPS,
3402
+ `Refining ${theme.count(newCommits.length)} work item${newCommits.length > 1 ? "s" : ""} with AI`
3403
+ );
3404
+ refineSpinner.start();
3405
+ const refineRequest = {
3406
+ brags: newCommits.map((c) => ({
3407
+ text: c.message,
3408
+ date: c.date,
3409
+ title: c.message.split("\n")[0]
3410
+ }))
3411
+ };
3412
+ const refineResponse = await apiService.refineBrags(refineRequest);
3413
+ let refinedBrags = refineResponse.refined_brags;
3414
+ succeedStepSpinner(refineSpinner, 4, TOTAL_STEPS, "Work items refined successfully");
3415
+ logger.log("");
3416
+ logger.info("Preview of refined brags:");
3417
+ logger.log("");
3418
+ logger.log(formatRefinedCommitsTable(refinedBrags, newCommits));
3419
+ logger.log("");
3420
+ const acceptedBrags = await promptReviewBrags(refinedBrags, newCommits);
3421
+ if (acceptedBrags.length === 0) {
3422
+ logger.log("");
3423
+ logger.info(theme.secondary("No brags selected for creation. Sync cancelled."));
3424
+ logger.log("");
3425
+ return;
3426
+ }
3427
+ logger.log("");
3428
+ let selectedOrgId = null;
3429
+ const userInfo = authService.getUserInfo();
3430
+ if (userInfo?.id) {
3431
+ try {
3432
+ const orgsResponse = await apiService.listUserOrganisations(userInfo.id);
3433
+ if (orgsResponse.items.length > 0) {
3434
+ selectedOrgId = await promptSelectOrganisation(orgsResponse.items);
3435
+ logger.log("");
3436
+ }
3437
+ } catch {
3438
+ logger.debug("Failed to fetch organisations, skipping org selection");
2452
3439
  }
2453
- currentBrag = editedBrag;
2454
- console.log("\n" + theme.success("\u2713 Changes saved. Review your edits:") + "\n");
2455
3440
  }
3441
+ const createSpinner2 = createStepSpinner(
3442
+ 5,
3443
+ TOTAL_STEPS,
3444
+ `Creating ${theme.count(acceptedBrags.length)} brag${acceptedBrags.length > 1 ? "s" : ""}`
3445
+ );
3446
+ createSpinner2.start();
3447
+ const createRequest = {
3448
+ brags: acceptedBrags.map((refined, index) => {
3449
+ const originalCommit = newCommits[index];
3450
+ return {
3451
+ commit_sha: originalCommit?.sha || `brag-${index}`,
3452
+ title: refined.refined_title,
3453
+ description: refined.refined_description,
3454
+ tags: refined.suggested_tags,
3455
+ repository: repoInfo.url,
3456
+ date: originalCommit?.date || "",
3457
+ commit_url: originalCommit?.url || "",
3458
+ impact_score: refined.suggested_impactLevel,
3459
+ impact_description: refined.impact_description,
3460
+ attachments: originalCommit?.url ? [originalCommit.url] : [],
3461
+ orgId: selectedOrgId || void 0
3462
+ };
3463
+ })
3464
+ };
3465
+ const createResponse = await apiService.createBrags(createRequest);
3466
+ succeedStepSpinner(
3467
+ createSpinner2,
3468
+ 5,
3469
+ TOTAL_STEPS,
3470
+ `Created ${theme.count(createResponse.created)} brag${createResponse.created > 1 ? "s" : ""}`
3471
+ );
3472
+ logger.log("");
3473
+ logger.log(boxen6(formatSuccessMessage(createResponse.created), boxStyles.success));
3474
+ } catch (error) {
3475
+ if (error instanceof CancelPromptError) {
3476
+ logger.log("");
3477
+ logger.info(theme.secondary("Sync cancelled."));
3478
+ logger.log("");
3479
+ return;
3480
+ }
3481
+ const err = error;
3482
+ logger.log("");
3483
+ logger.log(boxen6(formatErrorMessage(err.message, getErrorHint2(err)), boxStyles.error));
3484
+ process.exit(1);
2456
3485
  }
2457
- return acceptedBrags;
2458
3486
  }
2459
-
2460
- // src/utils/auth-helper.ts
2461
- async function ensureAuthenticated() {
2462
- const isAuthenticated = await authService.isAuthenticated();
2463
- if (isAuthenticated) {
2464
- return true;
3487
+ function getErrorHint2(error) {
3488
+ if (error.name === "GitHubError") {
3489
+ return 'Make sure you are in a GitHub repository and have authenticated with "gh auth login"';
2465
3490
  }
2466
- logger.log("");
2467
- logger.log(
2468
- boxen5(
2469
- theme.warning("Not authenticated") + "\n\nYou need to be logged in to use this command.\n\nWould you like to authenticate now?",
2470
- boxStyles.warning
2471
- )
2472
- );
2473
- logger.log("");
2474
- const shouldAuth = await promptConfirm("Authenticate now?", true);
2475
- if (!shouldAuth) {
2476
- logger.log("");
2477
- logger.info(
2478
- theme.secondary("Authentication skipped. Run ") + theme.command("bragduck init") + theme.secondary(" when you're ready to authenticate.")
2479
- );
2480
- logger.log("");
2481
- return false;
3491
+ if (error.name === "GitError") {
3492
+ return "Make sure you are in a git repository";
2482
3493
  }
2483
- try {
2484
- await initCommand();
2485
- return true;
2486
- } catch {
2487
- return false;
3494
+ if (error.name === "TokenExpiredError" || error.name === "AuthenticationError") {
3495
+ return 'Run "bragduck auth login" to login again';
3496
+ }
3497
+ if (error.name === "NetworkError") {
3498
+ return "Check your internet connection and try again";
2488
3499
  }
3500
+ if (error.name === "ApiError") {
3501
+ return "The server might be experiencing issues. Try again later";
3502
+ }
3503
+ return "Try running with DEBUG=* for more information";
2489
3504
  }
2490
3505
 
2491
- // src/commands/scan.ts
2492
- init_auth_service();
2493
-
2494
- // src/ui/spinners.ts
3506
+ // src/commands/logout.ts
2495
3507
  init_esm_shims();
3508
+ init_auth_service();
3509
+ init_logger();
3510
+ import { confirm as confirm2 } from "@inquirer/prompts";
3511
+ import boxen7 from "boxen";
3512
+ import chalk6 from "chalk";
2496
3513
  import ora3 from "ora";
2497
- function createSpinner(text) {
2498
- return ora3({
2499
- text,
2500
- color: "cyan",
2501
- spinner: "dots"
2502
- });
2503
- }
2504
- function createStepSpinner(currentStep, totalSteps, text) {
2505
- const stepIndicator = theme.step(currentStep, totalSteps);
2506
- return ora3({
2507
- text: `${stepIndicator} ${text}`,
2508
- color: "cyan",
2509
- spinner: "dots"
3514
+ async function logoutCommand() {
3515
+ const isAuthenticated = await authService.isAuthenticated();
3516
+ if (!isAuthenticated) {
3517
+ logger.log(
3518
+ boxen7(
3519
+ `${chalk6.yellow("Not currently authenticated")}
3520
+
3521
+ ${chalk6.dim("Nothing to logout from")}`,
3522
+ {
3523
+ padding: 1,
3524
+ margin: 1,
3525
+ borderStyle: "round",
3526
+ borderColor: "yellow"
3527
+ }
3528
+ )
3529
+ );
3530
+ return;
3531
+ }
3532
+ const userInfo = authService.getUserInfo();
3533
+ const userName = userInfo?.name || "Unknown User";
3534
+ logger.log("");
3535
+ const shouldLogout = await confirm2({
3536
+ message: `Are you sure you want to logout? (${chalk6.cyan(userName)})`,
3537
+ default: false
2510
3538
  });
2511
- }
2512
- function fetchingBragsSpinner() {
2513
- return createSpinner("Fetching your brags...");
2514
- }
2515
- function succeedSpinner(spinner, text) {
2516
- if (text) {
2517
- spinner.succeed(colors.success(text));
2518
- } else {
2519
- spinner.succeed();
3539
+ if (!shouldLogout) {
3540
+ logger.info("Logout cancelled");
3541
+ return;
2520
3542
  }
2521
- }
2522
- function failSpinner(spinner, text) {
2523
- if (text) {
2524
- spinner.fail(colors.error(text));
2525
- } else {
2526
- spinner.fail();
3543
+ const spinner = ora3("Logging out...").start();
3544
+ try {
3545
+ await authService.logout();
3546
+ spinner.succeed("Logged out successfully");
3547
+ logger.log("");
3548
+ logger.log(
3549
+ boxen7(
3550
+ `${chalk6.green.bold("\u2713 Logged out successfully")}
3551
+
3552
+ ${chalk6.dim("Your credentials have been cleared")}
3553
+
3554
+ ${chalk6.dim("Run")} ${chalk6.cyan("bragduck init")} ${chalk6.dim("to login again")}`,
3555
+ {
3556
+ padding: 1,
3557
+ margin: 1,
3558
+ borderStyle: "round",
3559
+ borderColor: "green"
3560
+ }
3561
+ )
3562
+ );
3563
+ } catch {
3564
+ spinner.fail("Logout failed");
3565
+ logger.error("Failed to logout. Please try again.");
3566
+ process.exit(1);
2527
3567
  }
2528
3568
  }
2529
- function succeedStepSpinner(spinner, currentStep, totalSteps, text) {
2530
- const stepIndicator = theme.step(currentStep, totalSteps);
2531
- spinner.succeed(`${stepIndicator} ${colors.success(text)}`);
2532
- }
2533
- function failStepSpinner(spinner, currentStep, totalSteps, text) {
2534
- const stepIndicator = theme.step(currentStep, totalSteps);
2535
- spinner.fail(`${stepIndicator} ${colors.error(text)}`);
2536
- }
2537
3569
 
2538
3570
  // src/commands/scan.ts
3571
+ init_esm_shims();
3572
+ import boxen8 from "boxen";
3573
+ import chalk7 from "chalk";
3574
+ init_api_service();
3575
+ init_storage_service();
3576
+ init_logger();
3577
+ init_browser();
3578
+ init_auth_service();
2539
3579
  async function scanCommand(options = {}) {
3580
+ logger.log("");
3581
+ logger.log(
3582
+ boxen8(
3583
+ chalk7.yellow.bold("\u26A0 Deprecation Notice") + `
3584
+
3585
+ The ${chalk7.cyan("scan")} command is deprecated.
3586
+ Please use ${chalk7.cyan("bragduck sync")} instead.
3587
+
3588
+ ` + chalk7.dim("This command will be removed in v3.0.0"),
3589
+ {
3590
+ padding: 1,
3591
+ borderStyle: "round",
3592
+ borderColor: "yellow",
3593
+ dimBorder: true
3594
+ }
3595
+ )
3596
+ );
2540
3597
  logger.log("");
2541
3598
  const TOTAL_STEPS = 5;
2542
3599
  try {
@@ -2555,7 +3612,7 @@ async function scanCommand(options = {}) {
2555
3612
  logger.debug("FREE tier detected - blocking scan command");
2556
3613
  logger.log("");
2557
3614
  logger.log(
2558
- boxen6(
3615
+ boxen8(
2559
3616
  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!"),
2560
3617
  {
2561
3618
  ...boxStyles.warning,
@@ -2758,7 +3815,7 @@ async function scanCommand(options = {}) {
2758
3815
  `Created ${theme.count(createResponse.created)} brag${createResponse.created > 1 ? "s" : ""}`
2759
3816
  );
2760
3817
  logger.log("");
2761
- logger.log(boxen6(formatSuccessMessage(createResponse.created), boxStyles.success));
3818
+ logger.log(boxen8(formatSuccessMessage(createResponse.created), boxStyles.success));
2762
3819
  } catch (error) {
2763
3820
  if (error instanceof CancelPromptError) {
2764
3821
  logger.log("");
@@ -2768,11 +3825,11 @@ async function scanCommand(options = {}) {
2768
3825
  }
2769
3826
  const err = error;
2770
3827
  logger.log("");
2771
- logger.log(boxen6(formatErrorMessage(err.message, getErrorHint2(err)), boxStyles.error));
3828
+ logger.log(boxen8(formatErrorMessage(err.message, getErrorHint3(err)), boxStyles.error));
2772
3829
  process.exit(1);
2773
3830
  }
2774
3831
  }
2775
- function getErrorHint2(error) {
3832
+ function getErrorHint3(error) {
2776
3833
  if (error.name === "GitHubError") {
2777
3834
  return 'Make sure you are in a GitHub repository and have authenticated with "gh auth login"';
2778
3835
  }
@@ -2794,7 +3851,7 @@ function getErrorHint2(error) {
2794
3851
  // src/commands/list.ts
2795
3852
  init_esm_shims();
2796
3853
  init_api_service();
2797
- import boxen7 from "boxen";
3854
+ import boxen9 from "boxen";
2798
3855
  import Table2 from "cli-table3";
2799
3856
  init_logger();
2800
3857
  async function listCommand(options = {}) {
@@ -2861,7 +3918,7 @@ async function listCommand(options = {}) {
2861
3918
  } catch (error) {
2862
3919
  const err = error;
2863
3920
  logger.log("");
2864
- logger.log(boxen7(formatErrorMessage(err.message, getErrorHint3(err)), boxStyles.error));
3921
+ logger.log(boxen9(formatErrorMessage(err.message, getErrorHint4(err)), boxStyles.error));
2865
3922
  process.exit(1);
2866
3923
  }
2867
3924
  }
@@ -2914,7 +3971,7 @@ function extractRepoName(url) {
2914
3971
  return url;
2915
3972
  }
2916
3973
  }
2917
- function getErrorHint3(error) {
3974
+ function getErrorHint4(error) {
2918
3975
  if (error.name === "TokenExpiredError" || error.name === "AuthenticationError") {
2919
3976
  return 'Run "bragduck init" to login again';
2920
3977
  }
@@ -2932,8 +3989,8 @@ init_esm_shims();
2932
3989
  init_storage_service();
2933
3990
  init_logger();
2934
3991
  init_constants();
2935
- import boxen8 from "boxen";
2936
- import chalk6 from "chalk";
3992
+ import boxen10 from "boxen";
3993
+ import chalk8 from "chalk";
2937
3994
  import Table3 from "cli-table3";
2938
3995
  init_errors();
2939
3996
  var VALID_CONFIG_KEYS = Object.values(CONFIG_KEYS);
@@ -2968,7 +4025,7 @@ async function configCommand(subcommand, key, value) {
2968
4025
  const err = error;
2969
4026
  logger.log("");
2970
4027
  logger.log(
2971
- boxen8(formatErrorMessage(err.message, getConfigHint(err)), {
4028
+ boxen10(formatErrorMessage(err.message, getConfigHint(err)), {
2972
4029
  padding: 1,
2973
4030
  margin: 1,
2974
4031
  borderStyle: "round",
@@ -2984,7 +4041,7 @@ async function handleListConfig() {
2984
4041
  autoVersionCheck: storageService.getConfig("autoVersionCheck")
2985
4042
  };
2986
4043
  const table = new Table3({
2987
- head: [chalk6.cyan("Key"), chalk6.cyan("Value"), chalk6.cyan("Default")],
4044
+ head: [chalk8.cyan("Key"), chalk8.cyan("Value"), chalk8.cyan("Default")],
2988
4045
  colWidths: [25, 40, 40],
2989
4046
  wordWrap: true,
2990
4047
  style: {
@@ -2993,36 +4050,36 @@ async function handleListConfig() {
2993
4050
  }
2994
4051
  });
2995
4052
  table.push([
2996
- chalk6.white("defaultCommitDays"),
2997
- chalk6.yellow(String(config2.defaultCommitDays)),
2998
- chalk6.dim(String(DEFAULT_CONFIG.defaultCommitDays))
4053
+ chalk8.white("defaultCommitDays"),
4054
+ chalk8.yellow(String(config2.defaultCommitDays)),
4055
+ chalk8.dim(String(DEFAULT_CONFIG.defaultCommitDays))
2999
4056
  ]);
3000
4057
  table.push([
3001
- chalk6.white("autoVersionCheck"),
3002
- chalk6.yellow(String(config2.autoVersionCheck)),
3003
- chalk6.dim(String(DEFAULT_CONFIG.autoVersionCheck))
4058
+ chalk8.white("autoVersionCheck"),
4059
+ chalk8.yellow(String(config2.autoVersionCheck)),
4060
+ chalk8.dim(String(DEFAULT_CONFIG.autoVersionCheck))
3004
4061
  ]);
3005
4062
  logger.info("Current configuration:");
3006
4063
  logger.log("");
3007
4064
  logger.log(table.toString());
3008
4065
  logger.log("");
3009
- logger.info(chalk6.dim("To change a value: ") + chalk6.cyan("bragduck config set <key> <value>"));
4066
+ logger.info(chalk8.dim("To change a value: ") + chalk8.cyan("bragduck config set <key> <value>"));
3010
4067
  logger.log("");
3011
4068
  }
3012
4069
  async function handleGetConfig(key) {
3013
4070
  validateConfigKey(key);
3014
4071
  const value = storageService.getConfig(key);
3015
4072
  const defaultValue = DEFAULT_CONFIG[key];
3016
- logger.info(`Configuration for ${chalk6.cyan(key)}:`);
4073
+ logger.info(`Configuration for ${chalk8.cyan(key)}:`);
3017
4074
  logger.log("");
3018
- logger.log(` ${chalk6.white("Current:")} ${chalk6.yellow(String(value))}`);
3019
- logger.log(` ${chalk6.white("Default:")} ${chalk6.dim(String(defaultValue))}`);
4075
+ logger.log(` ${chalk8.white("Current:")} ${chalk8.yellow(String(value))}`);
4076
+ logger.log(` ${chalk8.white("Default:")} ${chalk8.dim(String(defaultValue))}`);
3020
4077
  logger.log("");
3021
4078
  if (value === defaultValue) {
3022
- logger.info(chalk6.dim("Using default value"));
4079
+ logger.info(chalk8.dim("Using default value"));
3023
4080
  } else {
3024
4081
  logger.info(
3025
- chalk6.dim("Custom value set. Reset with: ") + chalk6.cyan(`bragduck config set ${key} ${defaultValue}`)
4082
+ chalk8.dim("Custom value set. Reset with: ") + chalk8.cyan(`bragduck config set ${key} ${defaultValue}`)
3026
4083
  );
3027
4084
  }
3028
4085
  logger.log("");
@@ -3032,10 +4089,10 @@ async function handleSetConfig(key, value) {
3032
4089
  const typedValue = validateAndConvertValue(key, value);
3033
4090
  storageService.setConfig(key, typedValue);
3034
4091
  logger.log(
3035
- boxen8(
3036
- `${chalk6.green.bold("\u2713 Configuration updated")}
4092
+ boxen10(
4093
+ `${chalk8.green.bold("\u2713 Configuration updated")}
3037
4094
 
3038
- ${chalk6.white(key)}: ${chalk6.yellow(String(typedValue))}`,
4095
+ ${chalk8.white(key)}: ${chalk8.yellow(String(typedValue))}`,
3039
4096
  {
3040
4097
  padding: 1,
3041
4098
  margin: 1,
@@ -3101,6 +4158,22 @@ var packageJsonPath = join7(__dirname6, "../../package.json");
3101
4158
  var packageJson = JSON.parse(readFileSync5(packageJsonPath, "utf-8"));
3102
4159
  var program = new Command();
3103
4160
  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)");
4161
+ program.command("auth [subcommand]").description("Manage authentication (subcommands: login, status, bitbucket)").action(async (subcommand) => {
4162
+ try {
4163
+ await authCommand(subcommand);
4164
+ } catch (error) {
4165
+ console.error(error);
4166
+ process.exit(1);
4167
+ }
4168
+ });
4169
+ program.command("sync").description("Sync work items and create brags").option("-d, --days <number>", "Number of days to scan", (val) => parseInt(val, 10)).option("-a, --all", "Include all items (not just current user)").option("-s, --source <type>", "Explicit source type (github)").action(async (options) => {
4170
+ try {
4171
+ await syncCommand(options);
4172
+ } catch (error) {
4173
+ console.error(error);
4174
+ process.exit(1);
4175
+ }
4176
+ });
3104
4177
  program.command("init").description("Authenticate with Bragduck").action(async () => {
3105
4178
  try {
3106
4179
  await initCommand();
@@ -3109,7 +4182,7 @@ program.command("init").description("Authenticate with Bragduck").action(async (
3109
4182
  process.exit(1);
3110
4183
  }
3111
4184
  });
3112
- program.command("scan").description("Scan git commits and create brags").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) => {
4185
+ 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) => {
3113
4186
  try {
3114
4187
  await scanCommand(options);
3115
4188
  } catch (error) {