@bragduck/cli 2.3.11 → 2.4.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
  */
@@ -932,12 +1008,12 @@ __export(version_exports, {
932
1008
  });
933
1009
  import { readFileSync as readFileSync3 } from "fs";
934
1010
  import { fileURLToPath as fileURLToPath4 } from "url";
935
- import { dirname as dirname3, join as join5 } from "path";
1011
+ import { dirname as dirname3, join as join4 } from "path";
936
1012
  import chalk4 from "chalk";
937
- import boxen3 from "boxen";
1013
+ import boxen2 from "boxen";
938
1014
  function getCurrentVersion() {
939
1015
  try {
940
- const packageJsonPath2 = join5(__dirname4, "../../package.json");
1016
+ const packageJsonPath2 = join4(__dirname4, "../../package.json");
941
1017
  const packageJson2 = JSON.parse(readFileSync3(packageJsonPath2, "utf-8"));
942
1018
  return packageJson2.version;
943
1019
  } catch {
@@ -1003,7 +1079,7 @@ Latest version: ${chalk4.green(latestVersion)}
1003
1079
  Update with: ${chalk4.cyan("npm install -g @bragduck/cli@latest")}`;
1004
1080
  console.log("");
1005
1081
  console.log(
1006
- boxen3(message, {
1082
+ boxen2(message, {
1007
1083
  padding: 1,
1008
1084
  margin: { top: 0, right: 1, bottom: 0, left: 1 },
1009
1085
  borderStyle: "round",
@@ -1039,10 +1115,10 @@ import { ofetch as ofetch2 } from "ofetch";
1039
1115
  import { readFileSync as readFileSync4 } from "fs";
1040
1116
  import { fileURLToPath as fileURLToPath5 } from "url";
1041
1117
  import { URLSearchParams as URLSearchParams2 } from "url";
1042
- import { dirname as dirname4, join as join6 } from "path";
1118
+ import { dirname as dirname4, join as join5 } from "path";
1043
1119
  function getCliVersion() {
1044
1120
  try {
1045
- const packageJsonPath2 = join6(__dirname5, "../../package.json");
1121
+ const packageJsonPath2 = join5(__dirname5, "../../package.json");
1046
1122
  const packageJson2 = JSON.parse(readFileSync4(packageJsonPath2, "utf-8"));
1047
1123
  return packageJson2.version;
1048
1124
  } catch {
@@ -1354,29 +1430,202 @@ import { readFileSync as readFileSync5 } from "fs";
1354
1430
  import { fileURLToPath as fileURLToPath6 } from "url";
1355
1431
  import { dirname as dirname5, join as join7 } from "path";
1356
1432
 
1357
- // src/commands/init.ts
1433
+ // src/commands/auth.ts
1358
1434
  init_esm_shims();
1359
1435
  init_auth_service();
1436
+ init_storage_service();
1360
1437
  init_logger();
1361
- import ora from "ora";
1362
1438
  import boxen from "boxen";
1439
+ import chalk3 from "chalk";
1440
+
1441
+ // src/ui/theme.ts
1442
+ init_esm_shims();
1363
1443
  import chalk2 from "chalk";
1364
- async function initCommand() {
1444
+ var colors = {
1445
+ // Primary colors for main actions and interactive elements
1446
+ primary: chalk2.cyan,
1447
+ // Success states
1448
+ success: chalk2.green,
1449
+ successBold: chalk2.green.bold,
1450
+ // Warning states
1451
+ warning: chalk2.yellow,
1452
+ warningBold: chalk2.yellow.bold,
1453
+ // Error states
1454
+ error: chalk2.red,
1455
+ errorBold: chalk2.red.bold,
1456
+ // Info and metadata
1457
+ info: chalk2.gray,
1458
+ infoDim: chalk2.dim,
1459
+ // Highlighted/important data
1460
+ highlight: chalk2.yellow.bold,
1461
+ highlightCyan: chalk2.cyan.bold,
1462
+ // Text emphasis
1463
+ bold: chalk2.bold,
1464
+ dim: chalk2.dim,
1465
+ // Special purpose
1466
+ white: chalk2.white,
1467
+ gray: chalk2.gray,
1468
+ // Links and URLs
1469
+ link: chalk2.blue.underline
1470
+ };
1471
+ var theme = {
1472
+ /**
1473
+ * Format command names and CLI actions
1474
+ */
1475
+ command: (text) => colors.primary(text),
1476
+ /**
1477
+ * Format success messages
1478
+ */
1479
+ success: (text) => colors.success(`\u2713 ${text}`),
1480
+ successBold: (text) => colors.successBold(`\u2713 ${text}`),
1481
+ /**
1482
+ * Format error messages
1483
+ */
1484
+ error: (text) => colors.error(`\u2717 ${text}`),
1485
+ errorBold: (text) => colors.errorBold(`\u2717 ${text}`),
1486
+ /**
1487
+ * Format warning messages
1488
+ */
1489
+ warning: (text) => colors.warning(`\u26A0 ${text}`),
1490
+ /**
1491
+ * Format info messages
1492
+ */
1493
+ info: (text) => colors.info(text),
1494
+ /**
1495
+ * Format labels (e.g., "User:", "Email:")
1496
+ */
1497
+ label: (text) => colors.gray(`${text}:`),
1498
+ /**
1499
+ * Format values
1500
+ */
1501
+ value: (text) => colors.highlight(text),
1502
+ /**
1503
+ * Format counts and numbers
1504
+ */
1505
+ count: (num) => colors.highlightCyan(num.toString()),
1506
+ /**
1507
+ * Format file paths
1508
+ */
1509
+ path: (text) => colors.info(text),
1510
+ /**
1511
+ * Format step indicators (e.g., "Step 1/5:")
1512
+ */
1513
+ step: (current, total) => colors.primary(`[${current}/${total}]`),
1514
+ /**
1515
+ * Format progress text
1516
+ */
1517
+ progress: (text) => colors.infoDim(text),
1518
+ /**
1519
+ * Format emphasized text
1520
+ */
1521
+ emphasis: (text) => colors.bold(text),
1522
+ /**
1523
+ * Format de-emphasized/secondary text
1524
+ */
1525
+ secondary: (text) => colors.dim(text),
1526
+ /**
1527
+ * Format interactive elements (prompts, selections)
1528
+ */
1529
+ interactive: (text) => colors.primary(text),
1530
+ /**
1531
+ * Format keys in key-value pairs
1532
+ */
1533
+ key: (text) => colors.white(text)
1534
+ };
1535
+ var boxStyles = {
1536
+ success: {
1537
+ borderColor: "green",
1538
+ borderStyle: "round",
1539
+ padding: 1,
1540
+ margin: 1
1541
+ },
1542
+ error: {
1543
+ borderColor: "red",
1544
+ borderStyle: "round",
1545
+ padding: 1,
1546
+ margin: 1
1547
+ },
1548
+ warning: {
1549
+ borderColor: "yellow",
1550
+ borderStyle: "round",
1551
+ padding: 1,
1552
+ margin: 1
1553
+ },
1554
+ info: {
1555
+ borderColor: "cyan",
1556
+ borderStyle: "round",
1557
+ padding: 1,
1558
+ margin: 1
1559
+ },
1560
+ plain: {
1561
+ borderColor: "gray",
1562
+ borderStyle: "round",
1563
+ padding: 1,
1564
+ margin: 1
1565
+ }
1566
+ };
1567
+ var tableStyles = {
1568
+ default: {
1569
+ style: {
1570
+ head: [],
1571
+ border: ["gray"]
1572
+ }
1573
+ },
1574
+ compact: {
1575
+ style: {
1576
+ head: [],
1577
+ border: ["dim"],
1578
+ compact: true
1579
+ }
1580
+ }
1581
+ };
1582
+ var sizeIndicators = {
1583
+ small: colors.success("\u25CF"),
1584
+ medium: colors.warning("\u25CF"),
1585
+ large: colors.error("\u25CF")
1586
+ };
1587
+ function getSizeIndicator(insertions, deletions) {
1588
+ const total = insertions + deletions;
1589
+ if (total < 50) {
1590
+ return `${sizeIndicators.small} Small`;
1591
+ } else if (total < 200) {
1592
+ return `${sizeIndicators.medium} Medium`;
1593
+ } else {
1594
+ return `${sizeIndicators.large} Large`;
1595
+ }
1596
+ }
1597
+ function formatDiffStats(insertions, deletions) {
1598
+ return `${colors.success(`+${insertions}`)} ${colors.error(`-${deletions}`)}`;
1599
+ }
1600
+
1601
+ // src/commands/auth.ts
1602
+ async function authCommand(subcommand) {
1603
+ if (!subcommand || subcommand === "login") {
1604
+ await authLogin();
1605
+ } else if (subcommand === "status") {
1606
+ await authStatus();
1607
+ } else {
1608
+ logger.error(`Unknown auth subcommand: ${subcommand}`);
1609
+ logger.info("Available subcommands: login, status");
1610
+ process.exit(1);
1611
+ }
1612
+ }
1613
+ async function authLogin() {
1365
1614
  logger.log("");
1366
- logger.info("Starting authentication flow...");
1615
+ logger.info("Authenticating with Bragduck...");
1367
1616
  logger.log("");
1368
- const isAuthenticated = await authService.isAuthenticated();
1617
+ const isAuthenticated = await storageService.isServiceAuthenticated("bragduck");
1369
1618
  if (isAuthenticated) {
1370
1619
  const userInfo = authService.getUserInfo();
1371
1620
  if (userInfo) {
1372
1621
  logger.log(
1373
1622
  boxen(
1374
- `${chalk2.yellow("Already authenticated!")}
1623
+ `${chalk3.yellow("Already authenticated!")}
1375
1624
 
1376
- ${chalk2.gray("User:")} ${userInfo.name}
1377
- ${chalk2.gray("Email:")} ${userInfo.email}
1625
+ ${chalk3.gray("User:")} ${userInfo.name}
1626
+ ${chalk3.gray("Email:")} ${userInfo.email}
1378
1627
 
1379
- ${chalk2.dim("Run")} ${chalk2.cyan("bragduck logout")} ${chalk2.dim("to sign out")}`,
1628
+ ${chalk3.dim("Run")} ${chalk3.cyan("bragduck logout")} ${chalk3.dim("to sign out")}`,
1380
1629
  {
1381
1630
  padding: 1,
1382
1631
  margin: 1,
@@ -1386,26 +1635,20 @@ ${chalk2.dim("Run")} ${chalk2.cyan("bragduck logout")} ${chalk2.dim("to sign out
1386
1635
  )
1387
1636
  );
1388
1637
  logger.log("");
1389
- globalThis.setTimeout(() => {
1390
- process.exit(0);
1391
- }, 100);
1392
1638
  return;
1393
1639
  }
1394
1640
  }
1395
- const spinner = ora("Opening browser for authentication...").start();
1396
1641
  try {
1397
- spinner.text = "Waiting for authentication...";
1398
1642
  const userInfo = await authService.login();
1399
- spinner.succeed("Authentication successful!");
1400
1643
  logger.log("");
1401
1644
  logger.log(
1402
1645
  boxen(
1403
- `${chalk2.green.bold("\u2713 Successfully authenticated!")}
1646
+ `${chalk3.green.bold("\u2713 Successfully authenticated!")}
1404
1647
 
1405
- ${chalk2.gray("Welcome,")} ${chalk2.cyan(userInfo.name)}
1406
- ${chalk2.gray("Email:")} ${userInfo.email}
1648
+ ${chalk3.gray("Welcome,")} ${chalk3.cyan(userInfo.name)}
1649
+ ${chalk3.gray("Email:")} ${userInfo.email}
1407
1650
 
1408
- ${chalk2.dim("You can now use")} ${chalk2.cyan("bragduck scan")} ${chalk2.dim("to create brags!")}`,
1651
+ ${chalk3.dim("Use")} ${chalk3.cyan("bragduck sync")} ${chalk3.dim("to create brags")}`,
1409
1652
  {
1410
1653
  padding: 1,
1411
1654
  margin: 1,
@@ -1415,132 +1658,200 @@ ${chalk2.dim("You can now use")} ${chalk2.cyan("bragduck scan")} ${chalk2.dim("t
1415
1658
  )
1416
1659
  );
1417
1660
  logger.log("");
1418
- globalThis.setTimeout(() => {
1419
- process.exit(0);
1420
- }, 100);
1421
- return;
1422
1661
  } catch (error) {
1423
- spinner.fail("Authentication failed");
1424
- logger.log("");
1425
1662
  const err = error;
1663
+ logger.log("");
1426
1664
  logger.log(
1427
- boxen(
1428
- `${chalk2.red.bold("\u2717 Authentication Failed")}
1429
-
1430
- ${err.message}
1665
+ boxen(`${chalk3.red.bold("\u2717 Authentication Failed")}
1431
1666
 
1432
- ${chalk2.dim("Hint:")} ${getErrorHint(err)}`,
1433
- {
1434
- padding: 1,
1435
- margin: 1,
1436
- borderStyle: "round",
1437
- borderColor: "red"
1438
- }
1439
- )
1667
+ ${err.message}`, {
1668
+ padding: 1,
1669
+ margin: 1,
1670
+ borderStyle: "round",
1671
+ borderColor: "red"
1672
+ })
1440
1673
  );
1441
1674
  process.exit(1);
1442
1675
  }
1443
1676
  }
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";
1677
+ async function authStatus() {
1678
+ logger.log("");
1679
+ logger.info("Authentication Status:");
1680
+ logger.log("");
1681
+ const services = await storageService.getAuthenticatedServices();
1682
+ if (services.length === 0) {
1683
+ logger.info(theme.secondary("Not authenticated with any services"));
1684
+ logger.log("");
1685
+ logger.info(`Run ${theme.command("bragduck auth login")} to authenticate`);
1686
+ logger.log("");
1687
+ return;
1453
1688
  }
1454
- if (error.name === "NetworkError") {
1455
- return "Check your internet connection and firewall settings";
1689
+ const bragduckAuth = await storageService.isServiceAuthenticated("bragduck");
1690
+ if (bragduckAuth) {
1691
+ const userInfo = authService.getUserInfo();
1692
+ logger.info(`${theme.success("\u2713")} Bragduck: ${userInfo?.name || "Authenticated"}`);
1693
+ } else {
1694
+ logger.info(`${theme.error("\u2717")} Bragduck: Not authenticated`);
1456
1695
  }
1457
- if (error.name === "AuthenticationError") {
1458
- return "Verify your credentials and try again";
1696
+ for (const service of services) {
1697
+ if (service !== "bragduck") {
1698
+ logger.info(`${theme.success("\u2713")} ${service}: Authenticated`);
1699
+ }
1459
1700
  }
1460
- return "Try running the command again or check the logs with DEBUG=* bragduck init";
1701
+ logger.log("");
1461
1702
  }
1462
1703
 
1463
- // src/commands/logout.ts
1704
+ // src/commands/sync.ts
1464
1705
  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
1706
 
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";
1491
- 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
- try {
1502
- await authService.logout();
1503
- spinner.succeed("Logged out successfully");
1504
- logger.log("");
1505
- logger.log(
1506
- boxen2(
1507
- `${chalk3.green.bold("\u2713 Logged out successfully")}
1707
+ // node_modules/@inquirer/core/dist/esm/lib/errors.mjs
1708
+ init_esm_shims();
1709
+ var CancelPromptError = class extends Error {
1710
+ name = "CancelPromptError";
1711
+ message = "Prompt was canceled";
1712
+ };
1508
1713
 
1509
- ${chalk3.dim("Your credentials have been cleared")}
1714
+ // src/commands/sync.ts
1715
+ init_api_service();
1716
+ init_storage_service();
1717
+ init_auth_service();
1718
+ import boxen6 from "boxen";
1510
1719
 
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"
1720
+ // src/utils/source-detector.ts
1721
+ init_esm_shims();
1722
+ init_errors();
1723
+ import { exec as exec2 } from "child_process";
1724
+ import { promisify as promisify2 } from "util";
1725
+ var execAsync2 = promisify2(exec2);
1726
+ var SourceDetector = class {
1727
+ /**
1728
+ * Detect all possible sources from git remotes
1729
+ */
1730
+ async detectSources() {
1731
+ const detected = [];
1732
+ try {
1733
+ const { stdout } = await execAsync2("git remote -v");
1734
+ const remotes = this.parseRemotes(stdout);
1735
+ for (const remote of remotes) {
1736
+ const source = this.parseRemoteUrl(remote.url);
1737
+ if (source) {
1738
+ const isAuthenticated = await this.checkAuthentication(source.type);
1739
+ detected.push({
1740
+ ...source,
1741
+ remoteUrl: remote.url,
1742
+ isAuthenticated
1743
+ });
1517
1744
  }
1518
- )
1519
- );
1520
- } catch {
1521
- spinner.fail("Logout failed");
1522
- logger.error("Failed to logout. Please try again.");
1523
- process.exit(1);
1745
+ }
1746
+ } catch {
1747
+ throw new GitError("Not a git repository");
1748
+ }
1749
+ const recommended = this.selectRecommendedSource(detected);
1750
+ return { detected, recommended };
1524
1751
  }
1525
- }
1752
+ /**
1753
+ * Parse git remote -v output
1754
+ */
1755
+ parseRemotes(output) {
1756
+ const lines = output.split("\n").filter(Boolean);
1757
+ const remotes = /* @__PURE__ */ new Map();
1758
+ for (const line of lines) {
1759
+ const match = line.match(/^(\S+)\s+(\S+)\s+\(fetch\)$/);
1760
+ if (match && match[1] && match[2]) {
1761
+ remotes.set(match[1], match[2]);
1762
+ }
1763
+ }
1764
+ return Array.from(remotes.entries()).map(([name, url]) => ({ name, url }));
1765
+ }
1766
+ /**
1767
+ * Parse remote URL to detect source type
1768
+ */
1769
+ parseRemoteUrl(url) {
1770
+ if (url.includes("github.com")) {
1771
+ const match = url.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
1772
+ if (match && match[1] && match[2]) {
1773
+ return {
1774
+ type: "github",
1775
+ host: "github.com",
1776
+ owner: match[1],
1777
+ repo: match[2]
1778
+ };
1779
+ }
1780
+ }
1781
+ if (url.includes("gitlab.com")) {
1782
+ const match = url.match(/gitlab\.com[:/]([^/]+)\/([^/.]+)/);
1783
+ if (match && match[1] && match[2]) {
1784
+ return {
1785
+ type: "gitlab",
1786
+ host: "gitlab.com",
1787
+ owner: match[1],
1788
+ repo: match[2]
1789
+ };
1790
+ }
1791
+ }
1792
+ if (url.includes("bitbucket.org")) {
1793
+ const match = url.match(/bitbucket\.org[:/]([^/]+)\/([^/.]+)/);
1794
+ if (match && match[1] && match[2]) {
1795
+ return {
1796
+ type: "bitbucket",
1797
+ host: "bitbucket.org",
1798
+ owner: match[1],
1799
+ repo: match[2]
1800
+ };
1801
+ }
1802
+ }
1803
+ if (url.match(/\/scm\/|bitbucket\./)) {
1804
+ const match = url.match(/([^/:]+)[:/]scm\/([^/]+)\/([^/.]+)/);
1805
+ if (match && match[1] && match[2] && match[3]) {
1806
+ return {
1807
+ type: "atlassian",
1808
+ host: match[1],
1809
+ owner: match[2],
1810
+ repo: match[3]
1811
+ };
1812
+ }
1813
+ }
1814
+ return null;
1815
+ }
1816
+ /**
1817
+ * Check if user is authenticated with a specific source
1818
+ */
1819
+ async checkAuthentication(type) {
1820
+ try {
1821
+ if (type === "github") {
1822
+ await execAsync2("command gh auth status");
1823
+ return true;
1824
+ }
1825
+ return false;
1826
+ } catch {
1827
+ return false;
1828
+ }
1829
+ }
1830
+ /**
1831
+ * Select recommended source (prefer authenticated GitHub)
1832
+ */
1833
+ selectRecommendedSource(sources) {
1834
+ const authenticated = sources.find((s) => s.isAuthenticated);
1835
+ if (authenticated) return authenticated.type;
1836
+ const github = sources.find((s) => s.type === "github");
1837
+ if (github) return "github";
1838
+ return sources[0]?.type;
1839
+ }
1840
+ };
1841
+ var sourceDetector = new SourceDetector();
1526
1842
 
1527
- // src/commands/scan.ts
1843
+ // src/sync/adapter-factory.ts
1528
1844
  init_esm_shims();
1529
- import boxen6 from "boxen";
1530
1845
 
1531
- // node_modules/@inquirer/core/dist/esm/lib/errors.mjs
1846
+ // src/sync/github-adapter.ts
1532
1847
  init_esm_shims();
1533
- var CancelPromptError = class extends Error {
1534
- name = "CancelPromptError";
1535
- message = "Prompt was canceled";
1536
- };
1537
1848
 
1538
1849
  // src/services/github.service.ts
1539
1850
  init_esm_shims();
1540
1851
  init_errors();
1541
1852
  init_logger();
1542
- import { exec as exec2 } from "child_process";
1543
- import { promisify as promisify2 } from "util";
1853
+ import { exec as exec3 } from "child_process";
1854
+ import { promisify as promisify3 } from "util";
1544
1855
 
1545
1856
  // src/services/git.service.ts
1546
1857
  init_esm_shims();
@@ -1550,9 +1861,9 @@ import simpleGit from "simple-git";
1550
1861
  init_esm_shims();
1551
1862
  init_errors();
1552
1863
  import { existsSync as existsSync2 } from "fs";
1553
- import { join as join4 } from "path";
1864
+ import { join as join6 } from "path";
1554
1865
  function validateGitRepository(path2) {
1555
- const gitDir = join4(path2, ".git");
1866
+ const gitDir = join6(path2, ".git");
1556
1867
  if (!existsSync2(gitDir)) {
1557
1868
  throw new GitError(
1558
1869
  "Not a git repository. Please run this command from within a git repository.",
@@ -1765,7 +2076,7 @@ var GitService = class {
1765
2076
  var gitService = new GitService();
1766
2077
 
1767
2078
  // src/services/github.service.ts
1768
- var execAsync2 = promisify2(exec2);
2079
+ var execAsync3 = promisify3(exec3);
1769
2080
  var GitHubService = class {
1770
2081
  MAX_BODY_LENGTH = 5e3;
1771
2082
  PR_SEARCH_FIELDS = "number,title,body,author,mergedAt,additions,deletions,changedFiles,url,labels";
@@ -1774,7 +2085,7 @@ var GitHubService = class {
1774
2085
  */
1775
2086
  async checkGitHubCLI() {
1776
2087
  try {
1777
- await execAsync2("command gh --version");
2088
+ await execAsync3("command gh --version");
1778
2089
  return true;
1779
2090
  } catch {
1780
2091
  return false;
@@ -1796,7 +2107,7 @@ var GitHubService = class {
1796
2107
  */
1797
2108
  async checkAuthentication() {
1798
2109
  try {
1799
- await execAsync2("command gh auth status");
2110
+ await execAsync3("command gh auth status");
1800
2111
  return true;
1801
2112
  } catch {
1802
2113
  return false;
@@ -1821,7 +2132,7 @@ var GitHubService = class {
1821
2132
  await this.ensureGitHubCLI();
1822
2133
  await this.ensureAuthentication();
1823
2134
  await gitService.validateRepository();
1824
- const { stdout } = await execAsync2("command gh repo view --json url");
2135
+ const { stdout } = await execAsync3("command gh repo view --json url");
1825
2136
  const data = JSON.parse(stdout);
1826
2137
  if (!data.url) {
1827
2138
  throw new GitHubError("This repository is not hosted on GitHub", {
@@ -1853,7 +2164,7 @@ var GitHubService = class {
1853
2164
  async getRepositoryInfo() {
1854
2165
  try {
1855
2166
  await this.ensureGitHubCLI();
1856
- const { stdout } = await execAsync2(
2167
+ const { stdout } = await execAsync3(
1857
2168
  "command gh repo view --json owner,name,url,nameWithOwner"
1858
2169
  );
1859
2170
  const data = JSON.parse(stdout);
@@ -1878,7 +2189,7 @@ var GitHubService = class {
1878
2189
  */
1879
2190
  async getCurrentGitHubUser() {
1880
2191
  try {
1881
- const { stdout } = await execAsync2("command gh api user --jq .login");
2192
+ const { stdout } = await execAsync3("command gh api user --jq .login");
1882
2193
  return stdout.trim() || null;
1883
2194
  } catch {
1884
2195
  logger.debug("Failed to get GitHub user");
@@ -1904,7 +2215,7 @@ var GitHubService = class {
1904
2215
  const limitArg = limit ? `--limit ${limit}` : "";
1905
2216
  const command = `command gh pr list --state merged --json ${this.PR_SEARCH_FIELDS} --search "${searchQuery}" ${limitArg}`;
1906
2217
  logger.debug(`Running: ${command}`);
1907
- const { stdout } = await execAsync2(command);
2218
+ const { stdout } = await execAsync3(command);
1908
2219
  const prs = JSON.parse(stdout);
1909
2220
  logger.debug(`Found ${prs.length} merged PRs`);
1910
2221
  return prs;
@@ -1962,189 +2273,219 @@ ${truncatedBody}` : title;
1962
2273
  };
1963
2274
  var githubService = new GitHubService();
1964
2275
 
1965
- // src/commands/scan.ts
1966
- init_api_service();
1967
- init_storage_service();
2276
+ // src/sync/github-adapter.ts
2277
+ var GitHubSyncAdapter = class {
2278
+ name = "github";
2279
+ async validate() {
2280
+ await githubService.validateGitHubRepository();
2281
+ }
2282
+ async getRepositoryInfo() {
2283
+ const info = await githubService.getRepositoryInfo();
2284
+ return {
2285
+ owner: info.owner,
2286
+ name: info.name,
2287
+ fullName: info.fullName,
2288
+ url: info.url
2289
+ };
2290
+ }
2291
+ async fetchWorkItems(options) {
2292
+ let prs;
2293
+ if (options.author) {
2294
+ prs = await githubService.getMergedPRs({
2295
+ days: options.days,
2296
+ limit: options.limit,
2297
+ author: options.author
2298
+ });
2299
+ } else {
2300
+ prs = await githubService.getPRsByCurrentUser({
2301
+ days: options.days,
2302
+ limit: options.limit
2303
+ });
2304
+ }
2305
+ return prs.map((pr) => githubService.transformPRToCommit(pr));
2306
+ }
2307
+ async isAuthenticated() {
2308
+ try {
2309
+ await githubService.validateGitHubRepository();
2310
+ return true;
2311
+ } catch {
2312
+ return false;
2313
+ }
2314
+ }
2315
+ async getCurrentUser() {
2316
+ return githubService.getCurrentGitHubUser();
2317
+ }
2318
+ };
2319
+ var githubSyncAdapter = new GitHubSyncAdapter();
2320
+
2321
+ // src/sync/adapter-factory.ts
2322
+ var AdapterFactory = class {
2323
+ /**
2324
+ * Get adapter for a specific source type
2325
+ */
2326
+ static getAdapter(source) {
2327
+ switch (source) {
2328
+ case "github":
2329
+ return githubSyncAdapter;
2330
+ case "gitlab":
2331
+ case "bitbucket":
2332
+ case "atlassian":
2333
+ throw new Error(`${source} adapter not yet implemented`);
2334
+ default:
2335
+ throw new Error(`Unknown source type: ${source}`);
2336
+ }
2337
+ }
2338
+ /**
2339
+ * Check if adapter is available for source
2340
+ */
2341
+ static isSupported(source) {
2342
+ return source === "github";
2343
+ }
2344
+ };
2345
+
2346
+ // src/commands/sync.ts
1968
2347
  init_logger();
1969
- init_browser();
1970
2348
 
1971
2349
  // src/utils/auth-helper.ts
1972
2350
  init_esm_shims();
1973
2351
  init_auth_service();
1974
2352
  import boxen5 from "boxen";
2353
+
2354
+ // src/commands/init.ts
2355
+ init_esm_shims();
2356
+ init_auth_service();
2357
+ init_logger();
2358
+ import ora from "ora";
2359
+ import boxen3 from "boxen";
2360
+ import chalk5 from "chalk";
2361
+ async function initCommand() {
2362
+ logger.log("");
2363
+ logger.log(
2364
+ boxen3(
2365
+ chalk5.yellow.bold("\u26A0 Deprecation Notice") + `
2366
+
2367
+ The ${chalk5.cyan("init")} command is deprecated.
2368
+ Please use ${chalk5.cyan("bragduck auth login")} instead.
2369
+
2370
+ ` + chalk5.dim("This command will be removed in v3.0.0"),
2371
+ {
2372
+ padding: 1,
2373
+ borderStyle: "round",
2374
+ borderColor: "yellow",
2375
+ dimBorder: true
2376
+ }
2377
+ )
2378
+ );
2379
+ logger.log("");
2380
+ logger.info("Starting authentication flow...");
2381
+ logger.log("");
2382
+ const isAuthenticated = await authService.isAuthenticated();
2383
+ if (isAuthenticated) {
2384
+ const userInfo = authService.getUserInfo();
2385
+ if (userInfo) {
2386
+ logger.log(
2387
+ boxen3(
2388
+ `${chalk5.yellow("Already authenticated!")}
2389
+
2390
+ ${chalk5.gray("User:")} ${userInfo.name}
2391
+ ${chalk5.gray("Email:")} ${userInfo.email}
2392
+
2393
+ ${chalk5.dim("Run")} ${chalk5.cyan("bragduck logout")} ${chalk5.dim("to sign out")}`,
2394
+ {
2395
+ padding: 1,
2396
+ margin: 1,
2397
+ borderStyle: "round",
2398
+ borderColor: "yellow"
2399
+ }
2400
+ )
2401
+ );
2402
+ logger.log("");
2403
+ globalThis.setTimeout(() => {
2404
+ process.exit(0);
2405
+ }, 100);
2406
+ return;
2407
+ }
2408
+ }
2409
+ const spinner = ora("Opening browser for authentication...").start();
2410
+ try {
2411
+ spinner.text = "Waiting for authentication...";
2412
+ const userInfo = await authService.login();
2413
+ spinner.succeed("Authentication successful!");
2414
+ logger.log("");
2415
+ logger.log(
2416
+ boxen3(
2417
+ `${chalk5.green.bold("\u2713 Successfully authenticated!")}
2418
+
2419
+ ${chalk5.gray("Welcome,")} ${chalk5.cyan(userInfo.name)}
2420
+ ${chalk5.gray("Email:")} ${userInfo.email}
2421
+
2422
+ ${chalk5.dim("You can now use")} ${chalk5.cyan("bragduck scan")} ${chalk5.dim("to create brags!")}`,
2423
+ {
2424
+ padding: 1,
2425
+ margin: 1,
2426
+ borderStyle: "round",
2427
+ borderColor: "green"
2428
+ }
2429
+ )
2430
+ );
2431
+ logger.log("");
2432
+ globalThis.setTimeout(() => {
2433
+ process.exit(0);
2434
+ }, 100);
2435
+ return;
2436
+ } catch (error) {
2437
+ spinner.fail("Authentication failed");
2438
+ logger.log("");
2439
+ const err = error;
2440
+ logger.log(
2441
+ boxen3(
2442
+ `${chalk5.red.bold("\u2717 Authentication Failed")}
2443
+
2444
+ ${err.message}
2445
+
2446
+ ${chalk5.dim("Hint:")} ${getErrorHint(err)}`,
2447
+ {
2448
+ padding: 1,
2449
+ margin: 1,
2450
+ borderStyle: "round",
2451
+ borderColor: "red"
2452
+ }
2453
+ )
2454
+ );
2455
+ process.exit(1);
2456
+ }
2457
+ }
2458
+ function getErrorHint(error) {
2459
+ if (error.name === "OAuthError") {
2460
+ if (error.message.includes("timeout")) {
2461
+ return "Try again and complete the authentication within 2 minutes";
2462
+ }
2463
+ if (error.message.includes("CSRF")) {
2464
+ return "This might be a security issue. Try running the command again";
2465
+ }
2466
+ return "Check your internet connection and try again";
2467
+ }
2468
+ if (error.name === "NetworkError") {
2469
+ return "Check your internet connection and firewall settings";
2470
+ }
2471
+ if (error.name === "AuthenticationError") {
2472
+ return "Verify your credentials and try again";
2473
+ }
2474
+ return "Try running the command again or check the logs with DEBUG=* bragduck init";
2475
+ }
2476
+
2477
+ // src/utils/auth-helper.ts
1975
2478
  init_logger();
1976
2479
 
1977
2480
  // src/ui/prompts.ts
1978
2481
  init_esm_shims();
1979
- import { checkbox, confirm as confirm2, input, select, editor } from "@inquirer/prompts";
2482
+ import { checkbox, confirm, input, select, editor } from "@inquirer/prompts";
1980
2483
  import boxen4 from "boxen";
1981
2484
 
1982
2485
  // src/ui/formatters.ts
1983
2486
  init_esm_shims();
1984
2487
  import Table from "cli-table3";
1985
2488
  import terminalLink from "terminal-link";
1986
-
1987
- // src/ui/theme.ts
1988
- 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),
2022
- /**
2023
- * Format success messages
2024
- */
2025
- success: (text) => colors.success(`\u2713 ${text}`),
2026
- successBold: (text) => colors.successBold(`\u2713 ${text}`),
2027
- /**
2028
- * Format error messages
2029
- */
2030
- error: (text) => colors.error(`\u2717 ${text}`),
2031
- errorBold: (text) => colors.errorBold(`\u2717 ${text}`),
2032
- /**
2033
- * Format warning messages
2034
- */
2035
- warning: (text) => colors.warning(`\u26A0 ${text}`),
2036
- /**
2037
- * Format info messages
2038
- */
2039
- info: (text) => colors.info(text),
2040
- /**
2041
- * Format labels (e.g., "User:", "Email:")
2042
- */
2043
- label: (text) => colors.gray(`${text}:`),
2044
- /**
2045
- * Format values
2046
- */
2047
- value: (text) => colors.highlight(text),
2048
- /**
2049
- * Format counts and numbers
2050
- */
2051
- count: (num) => colors.highlightCyan(num.toString()),
2052
- /**
2053
- * Format file paths
2054
- */
2055
- path: (text) => colors.info(text),
2056
- /**
2057
- * Format step indicators (e.g., "Step 1/5:")
2058
- */
2059
- step: (current, total) => colors.primary(`[${current}/${total}]`),
2060
- /**
2061
- * Format progress text
2062
- */
2063
- progress: (text) => colors.infoDim(text),
2064
- /**
2065
- * Format emphasized text
2066
- */
2067
- emphasis: (text) => colors.bold(text),
2068
- /**
2069
- * Format de-emphasized/secondary text
2070
- */
2071
- secondary: (text) => colors.dim(text),
2072
- /**
2073
- * Format interactive elements (prompts, selections)
2074
- */
2075
- interactive: (text) => colors.primary(text),
2076
- /**
2077
- * Format keys in key-value pairs
2078
- */
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
2111
- }
2112
- };
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
2125
- }
2126
- }
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`;
2141
- }
2142
- }
2143
- function formatDiffStats(insertions, deletions) {
2144
- return `${colors.success(`+${insertions}`)} ${colors.error(`-${deletions}`)}`;
2145
- }
2146
-
2147
- // src/ui/formatters.ts
2148
2489
  function formatCommitChoice(commit) {
2149
2490
  let displaySha;
2150
2491
  if (commit.sha.startsWith("pr-")) {
@@ -2281,7 +2622,7 @@ async function promptSelectCommits(commits) {
2281
2622
  return selected;
2282
2623
  }
2283
2624
  async function promptConfirm(message, defaultValue = true) {
2284
- return await confirm2({
2625
+ return await confirm({
2285
2626
  message,
2286
2627
  default: defaultValue
2287
2628
  });
@@ -2420,123 +2761,465 @@ ${theme.label("PR Link")} ${colors.link(prUrl)}`;
2420
2761
  for (let j = i + 1; j < refinedBrags.length; j++) {
2421
2762
  acceptedBrags.push(refinedBrags[j]);
2422
2763
  }
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;
2764
+ reviewingBrag = false;
2765
+ i = refinedBrags.length - 1;
2766
+ break;
2767
+ }
2768
+ if (action === "skip") {
2769
+ reviewingBrag = false;
2770
+ continue;
2771
+ }
2772
+ if (action === "accept") {
2773
+ acceptedBrags.push(currentBrag);
2774
+ reviewingBrag = false;
2775
+ continue;
2776
+ }
2777
+ let editedBrag = { ...currentBrag };
2778
+ if (action === "edit-title" || action === "edit-both") {
2779
+ console.log("");
2780
+ const newTitle = await input({
2781
+ message: "Enter new title:",
2782
+ default: currentBrag.refined_title
2783
+ });
2784
+ editedBrag.refined_title = newTitle;
2785
+ }
2786
+ if (action === "edit-desc" || action === "edit-both") {
2787
+ console.log("");
2788
+ const newDesc = await editor({
2789
+ message: "Edit description (will open your default editor):",
2790
+ default: currentBrag.refined_description
2791
+ });
2792
+ editedBrag.refined_description = newDesc;
2793
+ }
2794
+ currentBrag = editedBrag;
2795
+ console.log("\n" + theme.success("\u2713 Changes saved. Review your edits:") + "\n");
2796
+ }
2797
+ }
2798
+ return acceptedBrags;
2799
+ }
2800
+
2801
+ // src/utils/auth-helper.ts
2802
+ async function ensureAuthenticated() {
2803
+ const isAuthenticated = await authService.isAuthenticated();
2804
+ if (isAuthenticated) {
2805
+ return true;
2806
+ }
2807
+ logger.log("");
2808
+ logger.log(
2809
+ boxen5(
2810
+ theme.warning("Not authenticated") + "\n\nYou need to be logged in to use this command.\n\nWould you like to authenticate now?",
2811
+ boxStyles.warning
2812
+ )
2813
+ );
2814
+ logger.log("");
2815
+ const shouldAuth = await promptConfirm("Authenticate now?", true);
2816
+ if (!shouldAuth) {
2817
+ logger.log("");
2818
+ logger.info(
2819
+ theme.secondary("Authentication skipped. Run ") + theme.command("bragduck init") + theme.secondary(" when you're ready to authenticate.")
2820
+ );
2821
+ logger.log("");
2822
+ return false;
2823
+ }
2824
+ try {
2825
+ await initCommand();
2826
+ return true;
2827
+ } catch {
2828
+ return false;
2829
+ }
2830
+ }
2831
+
2832
+ // src/ui/spinners.ts
2833
+ init_esm_shims();
2834
+ import ora2 from "ora";
2835
+ function createSpinner(text) {
2836
+ return ora2({
2837
+ text,
2838
+ color: "cyan",
2839
+ spinner: "dots"
2840
+ });
2841
+ }
2842
+ function createStepSpinner(currentStep, totalSteps, text) {
2843
+ const stepIndicator = theme.step(currentStep, totalSteps);
2844
+ return ora2({
2845
+ text: `${stepIndicator} ${text}`,
2846
+ color: "cyan",
2847
+ spinner: "dots"
2848
+ });
2849
+ }
2850
+ function fetchingBragsSpinner() {
2851
+ return createSpinner("Fetching your brags...");
2852
+ }
2853
+ function succeedSpinner(spinner, text) {
2854
+ if (text) {
2855
+ spinner.succeed(colors.success(text));
2856
+ } else {
2857
+ spinner.succeed();
2858
+ }
2859
+ }
2860
+ function failSpinner(spinner, text) {
2861
+ if (text) {
2862
+ spinner.fail(colors.error(text));
2863
+ } else {
2864
+ spinner.fail();
2865
+ }
2866
+ }
2867
+ function succeedStepSpinner(spinner, currentStep, totalSteps, text) {
2868
+ const stepIndicator = theme.step(currentStep, totalSteps);
2869
+ spinner.succeed(`${stepIndicator} ${colors.success(text)}`);
2870
+ }
2871
+ function failStepSpinner(spinner, currentStep, totalSteps, text) {
2872
+ const stepIndicator = theme.step(currentStep, totalSteps);
2873
+ spinner.fail(`${stepIndicator} ${colors.error(text)}`);
2874
+ }
2875
+
2876
+ // src/commands/sync.ts
2877
+ async function syncCommand(options = {}) {
2878
+ logger.log("");
2879
+ const TOTAL_STEPS = 5;
2880
+ try {
2881
+ const isAuthenticated = await ensureAuthenticated();
2882
+ if (!isAuthenticated) {
2883
+ process.exit(1);
2884
+ }
2885
+ logger.debug("Fetching subscription status...");
2886
+ const subscriptionStatus = await apiService.getSubscriptionStatus();
2887
+ logger.debug("Subscription status response:", JSON.stringify(subscriptionStatus, null, 2));
2888
+ if (subscriptionStatus.tier === "FREE") {
2889
+ logger.debug("FREE tier detected - blocking sync command");
2890
+ logger.log("");
2891
+ logger.log(
2892
+ boxen6(
2893
+ 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"),
2894
+ {
2895
+ ...boxStyles.warning,
2896
+ padding: 1,
2897
+ margin: 1
2898
+ }
2899
+ )
2900
+ );
2901
+ logger.log("");
2902
+ return;
2903
+ }
2904
+ logger.debug(`Subscription tier "${subscriptionStatus.tier}" - proceeding with sync`);
2905
+ const detectionSpinner = createStepSpinner(1, TOTAL_STEPS, "Detecting repository source");
2906
+ detectionSpinner.start();
2907
+ const detectionResult = await sourceDetector.detectSources();
2908
+ if (detectionResult.detected.length === 0) {
2909
+ failStepSpinner(detectionSpinner, 1, TOTAL_STEPS, "No supported sources detected");
2910
+ logger.log("");
2911
+ logger.info("Make sure you are in a git repository with a remote URL");
2912
+ return;
2913
+ }
2914
+ const sourceType = options.source || detectionResult.recommended;
2915
+ if (!sourceType) {
2916
+ failStepSpinner(detectionSpinner, 1, TOTAL_STEPS, "Could not determine source");
2917
+ return;
2918
+ }
2919
+ if (!AdapterFactory.isSupported(sourceType)) {
2920
+ failStepSpinner(detectionSpinner, 1, TOTAL_STEPS, `Source ${sourceType} not yet supported`);
2921
+ logger.log("");
2922
+ logger.info(`Currently supported: GitHub`);
2923
+ logger.info(`Coming soon: GitLab, Atlassian, Bitbucket`);
2924
+ return;
2925
+ }
2926
+ succeedStepSpinner(detectionSpinner, 1, TOTAL_STEPS, `Source: ${theme.value(sourceType)}`);
2927
+ logger.log("");
2928
+ const adapter = AdapterFactory.getAdapter(sourceType);
2929
+ const repoSpinner = createStepSpinner(2, TOTAL_STEPS, "Validating repository");
2930
+ repoSpinner.start();
2931
+ await adapter.validate();
2932
+ const repoInfo = await adapter.getRepositoryInfo();
2933
+ succeedStepSpinner(
2934
+ repoSpinner,
2935
+ 2,
2936
+ TOTAL_STEPS,
2937
+ `Repository: ${theme.value(repoInfo.fullName)}`
2938
+ );
2939
+ logger.log("");
2940
+ let days = options.days;
2941
+ if (!days) {
2942
+ const defaultDays = storageService.getConfig("defaultCommitDays");
2943
+ days = await promptDaysToScan(defaultDays);
2944
+ logger.log("");
2945
+ }
2946
+ const fetchSpinner = createStepSpinner(
2947
+ 3,
2948
+ TOTAL_STEPS,
2949
+ `Fetching work items from the last ${days} days`
2950
+ );
2951
+ fetchSpinner.start();
2952
+ const workItems = await adapter.fetchWorkItems({
2953
+ days,
2954
+ author: options.all ? void 0 : await adapter.getCurrentUser() || void 0
2955
+ });
2956
+ if (workItems.length === 0) {
2957
+ failStepSpinner(fetchSpinner, 3, TOTAL_STEPS, `No work items found in the last ${days} days`);
2958
+ logger.log("");
2959
+ logger.info("Try increasing the number of days or check your activity");
2960
+ return;
2961
+ }
2962
+ succeedStepSpinner(
2963
+ fetchSpinner,
2964
+ 3,
2965
+ TOTAL_STEPS,
2966
+ `Found ${theme.count(workItems.length)} work item${workItems.length > 1 ? "s" : ""}`
2967
+ );
2968
+ logger.log("");
2969
+ logger.log(formatCommitStats(workItems));
2970
+ logger.log("");
2971
+ let sortedCommits = [...workItems];
2972
+ if (workItems.length > 1) {
2973
+ const sortOption = await promptSortOption();
2974
+ logger.log("");
2975
+ if (sortOption === "date") {
2976
+ sortedCommits.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
2977
+ } else if (sortOption === "size") {
2978
+ sortedCommits.sort((a, b) => {
2979
+ const sizeA = (a.diffStats?.insertions || 0) + (a.diffStats?.deletions || 0);
2980
+ const sizeB = (b.diffStats?.insertions || 0) + (b.diffStats?.deletions || 0);
2981
+ return sizeB - sizeA;
2982
+ });
2983
+ } else if (sortOption === "files") {
2984
+ sortedCommits.sort((a, b) => {
2985
+ const filesA = a.diffStats?.filesChanged || 0;
2986
+ const filesB = b.diffStats?.filesChanged || 0;
2987
+ return filesB - filesA;
2988
+ });
2989
+ }
2990
+ }
2991
+ const selectedShas = await promptSelectCommits(sortedCommits);
2992
+ if (selectedShas.length === 0) {
2993
+ logger.log("");
2994
+ logger.info(theme.secondary("No work items selected. Sync cancelled."));
2995
+ logger.log("");
2996
+ return;
2997
+ }
2998
+ const selectedCommits = sortedCommits.filter((c) => selectedShas.includes(c.sha));
2999
+ logger.log(formatSelectionSummary(selectedCommits.length, selectedCommits));
3000
+ logger.log("");
3001
+ const existingBrags = await apiService.listBrags({ limit: 100 });
3002
+ logger.debug(`Fetched ${existingBrags.brags.length} existing brags`);
3003
+ const existingUrls = new Set(existingBrags.brags.flatMap((b) => b.attachments || []));
3004
+ logger.debug(`Existing URLs in attachments: ${existingUrls.size}`);
3005
+ const duplicates = selectedCommits.filter((c) => c.url && existingUrls.has(c.url));
3006
+ const newCommits = selectedCommits.filter((c) => !c.url || !existingUrls.has(c.url));
3007
+ logger.debug(`Duplicates: ${duplicates.length}, New: ${newCommits.length}`);
3008
+ if (duplicates.length > 0) {
3009
+ logger.log("");
3010
+ logger.info(
3011
+ colors.warning(
3012
+ `${duplicates.length} work item${duplicates.length > 1 ? "s" : ""} already added to Bragduck - skipping`
3013
+ )
3014
+ );
3015
+ logger.log("");
3016
+ }
3017
+ if (newCommits.length === 0) {
3018
+ logger.log("");
3019
+ logger.info(
3020
+ theme.secondary("All selected work items already exist in Bragduck. Nothing to refine.")
3021
+ );
3022
+ logger.log("");
3023
+ return;
3024
+ }
3025
+ const refineSpinner = createStepSpinner(
3026
+ 4,
3027
+ TOTAL_STEPS,
3028
+ `Refining ${theme.count(newCommits.length)} work item${newCommits.length > 1 ? "s" : ""} with AI`
3029
+ );
3030
+ refineSpinner.start();
3031
+ const refineRequest = {
3032
+ brags: newCommits.map((c) => ({
3033
+ text: c.message,
3034
+ date: c.date,
3035
+ title: c.message.split("\n")[0]
3036
+ }))
3037
+ };
3038
+ const refineResponse = await apiService.refineBrags(refineRequest);
3039
+ let refinedBrags = refineResponse.refined_brags;
3040
+ succeedStepSpinner(refineSpinner, 4, TOTAL_STEPS, "Work items refined successfully");
3041
+ logger.log("");
3042
+ logger.info("Preview of refined brags:");
3043
+ logger.log("");
3044
+ logger.log(formatRefinedCommitsTable(refinedBrags, newCommits));
3045
+ logger.log("");
3046
+ const acceptedBrags = await promptReviewBrags(refinedBrags, newCommits);
3047
+ if (acceptedBrags.length === 0) {
3048
+ logger.log("");
3049
+ logger.info(theme.secondary("No brags selected for creation. Sync cancelled."));
3050
+ logger.log("");
3051
+ return;
3052
+ }
3053
+ logger.log("");
3054
+ let selectedOrgId = null;
3055
+ const userInfo = authService.getUserInfo();
3056
+ if (userInfo?.id) {
3057
+ try {
3058
+ const orgsResponse = await apiService.listUserOrganisations(userInfo.id);
3059
+ if (orgsResponse.items.length > 0) {
3060
+ selectedOrgId = await promptSelectOrganisation(orgsResponse.items);
3061
+ logger.log("");
3062
+ }
3063
+ } catch {
3064
+ logger.debug("Failed to fetch organisations, skipping org selection");
2452
3065
  }
2453
- currentBrag = editedBrag;
2454
- console.log("\n" + theme.success("\u2713 Changes saved. Review your edits:") + "\n");
2455
3066
  }
3067
+ const createSpinner2 = createStepSpinner(
3068
+ 5,
3069
+ TOTAL_STEPS,
3070
+ `Creating ${theme.count(acceptedBrags.length)} brag${acceptedBrags.length > 1 ? "s" : ""}`
3071
+ );
3072
+ createSpinner2.start();
3073
+ const createRequest = {
3074
+ brags: acceptedBrags.map((refined, index) => {
3075
+ const originalCommit = newCommits[index];
3076
+ return {
3077
+ commit_sha: originalCommit?.sha || `brag-${index}`,
3078
+ title: refined.refined_title,
3079
+ description: refined.refined_description,
3080
+ tags: refined.suggested_tags,
3081
+ repository: repoInfo.url,
3082
+ date: originalCommit?.date || "",
3083
+ commit_url: originalCommit?.url || "",
3084
+ impact_score: refined.suggested_impactLevel,
3085
+ impact_description: refined.impact_description,
3086
+ attachments: originalCommit?.url ? [originalCommit.url] : [],
3087
+ orgId: selectedOrgId || void 0
3088
+ };
3089
+ })
3090
+ };
3091
+ const createResponse = await apiService.createBrags(createRequest);
3092
+ succeedStepSpinner(
3093
+ createSpinner2,
3094
+ 5,
3095
+ TOTAL_STEPS,
3096
+ `Created ${theme.count(createResponse.created)} brag${createResponse.created > 1 ? "s" : ""}`
3097
+ );
3098
+ logger.log("");
3099
+ logger.log(boxen6(formatSuccessMessage(createResponse.created), boxStyles.success));
3100
+ } catch (error) {
3101
+ if (error instanceof CancelPromptError) {
3102
+ logger.log("");
3103
+ logger.info(theme.secondary("Sync cancelled."));
3104
+ logger.log("");
3105
+ return;
3106
+ }
3107
+ const err = error;
3108
+ logger.log("");
3109
+ logger.log(boxen6(formatErrorMessage(err.message, getErrorHint2(err)), boxStyles.error));
3110
+ process.exit(1);
2456
3111
  }
2457
- return acceptedBrags;
2458
3112
  }
2459
-
2460
- // src/utils/auth-helper.ts
2461
- async function ensureAuthenticated() {
2462
- const isAuthenticated = await authService.isAuthenticated();
2463
- if (isAuthenticated) {
2464
- return true;
3113
+ function getErrorHint2(error) {
3114
+ if (error.name === "GitHubError") {
3115
+ return 'Make sure you are in a GitHub repository and have authenticated with "gh auth login"';
2465
3116
  }
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;
3117
+ if (error.name === "GitError") {
3118
+ return "Make sure you are in a git repository";
2482
3119
  }
2483
- try {
2484
- await initCommand();
2485
- return true;
2486
- } catch {
2487
- return false;
3120
+ if (error.name === "TokenExpiredError" || error.name === "AuthenticationError") {
3121
+ return 'Run "bragduck auth login" to login again';
3122
+ }
3123
+ if (error.name === "NetworkError") {
3124
+ return "Check your internet connection and try again";
2488
3125
  }
3126
+ if (error.name === "ApiError") {
3127
+ return "The server might be experiencing issues. Try again later";
3128
+ }
3129
+ return "Try running with DEBUG=* for more information";
2489
3130
  }
2490
3131
 
2491
- // src/commands/scan.ts
2492
- init_auth_service();
2493
-
2494
- // src/ui/spinners.ts
3132
+ // src/commands/logout.ts
2495
3133
  init_esm_shims();
3134
+ init_auth_service();
3135
+ init_logger();
3136
+ import { confirm as confirm2 } from "@inquirer/prompts";
3137
+ import boxen7 from "boxen";
3138
+ import chalk6 from "chalk";
2496
3139
  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"
3140
+ async function logoutCommand() {
3141
+ const isAuthenticated = await authService.isAuthenticated();
3142
+ if (!isAuthenticated) {
3143
+ logger.log(
3144
+ boxen7(
3145
+ `${chalk6.yellow("Not currently authenticated")}
3146
+
3147
+ ${chalk6.dim("Nothing to logout from")}`,
3148
+ {
3149
+ padding: 1,
3150
+ margin: 1,
3151
+ borderStyle: "round",
3152
+ borderColor: "yellow"
3153
+ }
3154
+ )
3155
+ );
3156
+ return;
3157
+ }
3158
+ const userInfo = authService.getUserInfo();
3159
+ const userName = userInfo?.name || "Unknown User";
3160
+ logger.log("");
3161
+ const shouldLogout = await confirm2({
3162
+ message: `Are you sure you want to logout? (${chalk6.cyan(userName)})`,
3163
+ default: false
2510
3164
  });
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();
3165
+ if (!shouldLogout) {
3166
+ logger.info("Logout cancelled");
3167
+ return;
2520
3168
  }
2521
- }
2522
- function failSpinner(spinner, text) {
2523
- if (text) {
2524
- spinner.fail(colors.error(text));
2525
- } else {
2526
- spinner.fail();
3169
+ const spinner = ora3("Logging out...").start();
3170
+ try {
3171
+ await authService.logout();
3172
+ spinner.succeed("Logged out successfully");
3173
+ logger.log("");
3174
+ logger.log(
3175
+ boxen7(
3176
+ `${chalk6.green.bold("\u2713 Logged out successfully")}
3177
+
3178
+ ${chalk6.dim("Your credentials have been cleared")}
3179
+
3180
+ ${chalk6.dim("Run")} ${chalk6.cyan("bragduck init")} ${chalk6.dim("to login again")}`,
3181
+ {
3182
+ padding: 1,
3183
+ margin: 1,
3184
+ borderStyle: "round",
3185
+ borderColor: "green"
3186
+ }
3187
+ )
3188
+ );
3189
+ } catch {
3190
+ spinner.fail("Logout failed");
3191
+ logger.error("Failed to logout. Please try again.");
3192
+ process.exit(1);
2527
3193
  }
2528
3194
  }
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
3195
 
2538
3196
  // src/commands/scan.ts
3197
+ init_esm_shims();
3198
+ import boxen8 from "boxen";
3199
+ import chalk7 from "chalk";
3200
+ init_api_service();
3201
+ init_storage_service();
3202
+ init_logger();
3203
+ init_browser();
3204
+ init_auth_service();
2539
3205
  async function scanCommand(options = {}) {
3206
+ logger.log("");
3207
+ logger.log(
3208
+ boxen8(
3209
+ chalk7.yellow.bold("\u26A0 Deprecation Notice") + `
3210
+
3211
+ The ${chalk7.cyan("scan")} command is deprecated.
3212
+ Please use ${chalk7.cyan("bragduck sync")} instead.
3213
+
3214
+ ` + chalk7.dim("This command will be removed in v3.0.0"),
3215
+ {
3216
+ padding: 1,
3217
+ borderStyle: "round",
3218
+ borderColor: "yellow",
3219
+ dimBorder: true
3220
+ }
3221
+ )
3222
+ );
2540
3223
  logger.log("");
2541
3224
  const TOTAL_STEPS = 5;
2542
3225
  try {
@@ -2555,7 +3238,7 @@ async function scanCommand(options = {}) {
2555
3238
  logger.debug("FREE tier detected - blocking scan command");
2556
3239
  logger.log("");
2557
3240
  logger.log(
2558
- boxen6(
3241
+ boxen8(
2559
3242
  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
3243
  {
2561
3244
  ...boxStyles.warning,
@@ -2758,7 +3441,7 @@ async function scanCommand(options = {}) {
2758
3441
  `Created ${theme.count(createResponse.created)} brag${createResponse.created > 1 ? "s" : ""}`
2759
3442
  );
2760
3443
  logger.log("");
2761
- logger.log(boxen6(formatSuccessMessage(createResponse.created), boxStyles.success));
3444
+ logger.log(boxen8(formatSuccessMessage(createResponse.created), boxStyles.success));
2762
3445
  } catch (error) {
2763
3446
  if (error instanceof CancelPromptError) {
2764
3447
  logger.log("");
@@ -2768,11 +3451,11 @@ async function scanCommand(options = {}) {
2768
3451
  }
2769
3452
  const err = error;
2770
3453
  logger.log("");
2771
- logger.log(boxen6(formatErrorMessage(err.message, getErrorHint2(err)), boxStyles.error));
3454
+ logger.log(boxen8(formatErrorMessage(err.message, getErrorHint3(err)), boxStyles.error));
2772
3455
  process.exit(1);
2773
3456
  }
2774
3457
  }
2775
- function getErrorHint2(error) {
3458
+ function getErrorHint3(error) {
2776
3459
  if (error.name === "GitHubError") {
2777
3460
  return 'Make sure you are in a GitHub repository and have authenticated with "gh auth login"';
2778
3461
  }
@@ -2794,7 +3477,7 @@ function getErrorHint2(error) {
2794
3477
  // src/commands/list.ts
2795
3478
  init_esm_shims();
2796
3479
  init_api_service();
2797
- import boxen7 from "boxen";
3480
+ import boxen9 from "boxen";
2798
3481
  import Table2 from "cli-table3";
2799
3482
  init_logger();
2800
3483
  async function listCommand(options = {}) {
@@ -2861,7 +3544,7 @@ async function listCommand(options = {}) {
2861
3544
  } catch (error) {
2862
3545
  const err = error;
2863
3546
  logger.log("");
2864
- logger.log(boxen7(formatErrorMessage(err.message, getErrorHint3(err)), boxStyles.error));
3547
+ logger.log(boxen9(formatErrorMessage(err.message, getErrorHint4(err)), boxStyles.error));
2865
3548
  process.exit(1);
2866
3549
  }
2867
3550
  }
@@ -2914,7 +3597,7 @@ function extractRepoName(url) {
2914
3597
  return url;
2915
3598
  }
2916
3599
  }
2917
- function getErrorHint3(error) {
3600
+ function getErrorHint4(error) {
2918
3601
  if (error.name === "TokenExpiredError" || error.name === "AuthenticationError") {
2919
3602
  return 'Run "bragduck init" to login again';
2920
3603
  }
@@ -2932,8 +3615,8 @@ init_esm_shims();
2932
3615
  init_storage_service();
2933
3616
  init_logger();
2934
3617
  init_constants();
2935
- import boxen8 from "boxen";
2936
- import chalk6 from "chalk";
3618
+ import boxen10 from "boxen";
3619
+ import chalk8 from "chalk";
2937
3620
  import Table3 from "cli-table3";
2938
3621
  init_errors();
2939
3622
  var VALID_CONFIG_KEYS = Object.values(CONFIG_KEYS);
@@ -2968,7 +3651,7 @@ async function configCommand(subcommand, key, value) {
2968
3651
  const err = error;
2969
3652
  logger.log("");
2970
3653
  logger.log(
2971
- boxen8(formatErrorMessage(err.message, getConfigHint(err)), {
3654
+ boxen10(formatErrorMessage(err.message, getConfigHint(err)), {
2972
3655
  padding: 1,
2973
3656
  margin: 1,
2974
3657
  borderStyle: "round",
@@ -2984,7 +3667,7 @@ async function handleListConfig() {
2984
3667
  autoVersionCheck: storageService.getConfig("autoVersionCheck")
2985
3668
  };
2986
3669
  const table = new Table3({
2987
- head: [chalk6.cyan("Key"), chalk6.cyan("Value"), chalk6.cyan("Default")],
3670
+ head: [chalk8.cyan("Key"), chalk8.cyan("Value"), chalk8.cyan("Default")],
2988
3671
  colWidths: [25, 40, 40],
2989
3672
  wordWrap: true,
2990
3673
  style: {
@@ -2993,36 +3676,36 @@ async function handleListConfig() {
2993
3676
  }
2994
3677
  });
2995
3678
  table.push([
2996
- chalk6.white("defaultCommitDays"),
2997
- chalk6.yellow(String(config2.defaultCommitDays)),
2998
- chalk6.dim(String(DEFAULT_CONFIG.defaultCommitDays))
3679
+ chalk8.white("defaultCommitDays"),
3680
+ chalk8.yellow(String(config2.defaultCommitDays)),
3681
+ chalk8.dim(String(DEFAULT_CONFIG.defaultCommitDays))
2999
3682
  ]);
3000
3683
  table.push([
3001
- chalk6.white("autoVersionCheck"),
3002
- chalk6.yellow(String(config2.autoVersionCheck)),
3003
- chalk6.dim(String(DEFAULT_CONFIG.autoVersionCheck))
3684
+ chalk8.white("autoVersionCheck"),
3685
+ chalk8.yellow(String(config2.autoVersionCheck)),
3686
+ chalk8.dim(String(DEFAULT_CONFIG.autoVersionCheck))
3004
3687
  ]);
3005
3688
  logger.info("Current configuration:");
3006
3689
  logger.log("");
3007
3690
  logger.log(table.toString());
3008
3691
  logger.log("");
3009
- logger.info(chalk6.dim("To change a value: ") + chalk6.cyan("bragduck config set <key> <value>"));
3692
+ logger.info(chalk8.dim("To change a value: ") + chalk8.cyan("bragduck config set <key> <value>"));
3010
3693
  logger.log("");
3011
3694
  }
3012
3695
  async function handleGetConfig(key) {
3013
3696
  validateConfigKey(key);
3014
3697
  const value = storageService.getConfig(key);
3015
3698
  const defaultValue = DEFAULT_CONFIG[key];
3016
- logger.info(`Configuration for ${chalk6.cyan(key)}:`);
3699
+ logger.info(`Configuration for ${chalk8.cyan(key)}:`);
3017
3700
  logger.log("");
3018
- logger.log(` ${chalk6.white("Current:")} ${chalk6.yellow(String(value))}`);
3019
- logger.log(` ${chalk6.white("Default:")} ${chalk6.dim(String(defaultValue))}`);
3701
+ logger.log(` ${chalk8.white("Current:")} ${chalk8.yellow(String(value))}`);
3702
+ logger.log(` ${chalk8.white("Default:")} ${chalk8.dim(String(defaultValue))}`);
3020
3703
  logger.log("");
3021
3704
  if (value === defaultValue) {
3022
- logger.info(chalk6.dim("Using default value"));
3705
+ logger.info(chalk8.dim("Using default value"));
3023
3706
  } else {
3024
3707
  logger.info(
3025
- chalk6.dim("Custom value set. Reset with: ") + chalk6.cyan(`bragduck config set ${key} ${defaultValue}`)
3708
+ chalk8.dim("Custom value set. Reset with: ") + chalk8.cyan(`bragduck config set ${key} ${defaultValue}`)
3026
3709
  );
3027
3710
  }
3028
3711
  logger.log("");
@@ -3032,10 +3715,10 @@ async function handleSetConfig(key, value) {
3032
3715
  const typedValue = validateAndConvertValue(key, value);
3033
3716
  storageService.setConfig(key, typedValue);
3034
3717
  logger.log(
3035
- boxen8(
3036
- `${chalk6.green.bold("\u2713 Configuration updated")}
3718
+ boxen10(
3719
+ `${chalk8.green.bold("\u2713 Configuration updated")}
3037
3720
 
3038
- ${chalk6.white(key)}: ${chalk6.yellow(String(typedValue))}`,
3721
+ ${chalk8.white(key)}: ${chalk8.yellow(String(typedValue))}`,
3039
3722
  {
3040
3723
  padding: 1,
3041
3724
  margin: 1,
@@ -3101,6 +3784,22 @@ var packageJsonPath = join7(__dirname6, "../../package.json");
3101
3784
  var packageJson = JSON.parse(readFileSync5(packageJsonPath, "utf-8"));
3102
3785
  var program = new Command();
3103
3786
  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)");
3787
+ program.command("auth [subcommand]").description("Manage authentication (subcommands: login, status)").action(async (subcommand) => {
3788
+ try {
3789
+ await authCommand(subcommand);
3790
+ } catch (error) {
3791
+ console.error(error);
3792
+ process.exit(1);
3793
+ }
3794
+ });
3795
+ 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) => {
3796
+ try {
3797
+ await syncCommand(options);
3798
+ } catch (error) {
3799
+ console.error(error);
3800
+ process.exit(1);
3801
+ }
3802
+ });
3104
3803
  program.command("init").description("Authenticate with Bragduck").action(async () => {
3105
3804
  try {
3106
3805
  await initCommand();
@@ -3109,7 +3808,7 @@ program.command("init").description("Authenticate with Bragduck").action(async (
3109
3808
  process.exit(1);
3110
3809
  }
3111
3810
  });
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) => {
3811
+ 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
3812
  try {
3114
3813
  await scanCommand(options);
3115
3814
  } catch (error) {