@bragduck/cli 2.0.2 → 2.0.23

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.
@@ -29,7 +29,7 @@ var init_constants = __esm({
29
29
  init_esm_shims();
30
30
  __filename = fileURLToPath2(import.meta.url);
31
31
  __dirname = dirname(__filename);
32
- config({ path: join(__dirname, "..", "..", ".env") });
32
+ config({ path: join(__dirname, "..", "..", ".env"), debug: false, quiet: true });
33
33
  APP_NAME = "bragduck";
34
34
  CONFIG_KEYS = {
35
35
  DEFAULT_COMMIT_DAYS: "defaultCommitDays",
@@ -52,14 +52,21 @@ var init_constants = __esm({
52
52
  INITIATE: "/v1/auth/cli/initiate",
53
53
  TOKEN: "/v1/auth/cli/token"
54
54
  },
55
- COMMITS: {
56
- REFINE: "/v1/commits/refine"
57
- },
58
55
  BRAGS: {
59
56
  CREATE: "/v1/brags",
60
- LIST: "/v1/brags"
57
+ LIST: "/v1/brags",
58
+ REFINE: "/v1/brags/refine"
61
59
  },
62
- VERSION: "/v1/cli/version"
60
+ SUBSCRIPTION: {
61
+ STATUS: "/v1/subscriptions/status"
62
+ },
63
+ VERSION: "/v1/cli/version",
64
+ /**
65
+ * @deprecated Use BRAGS.REFINE instead
66
+ */
67
+ COMMITS: {
68
+ REFINE: "/v1/commits/refine"
69
+ }
63
70
  };
64
71
  ENCRYPTION_CONFIG = {
65
72
  ALGORITHM: "aes-256-gcm",
@@ -312,11 +319,13 @@ var init_errors = __esm({
312
319
  "use strict";
313
320
  init_esm_shims();
314
321
  BragduckError = class extends Error {
322
+ code;
323
+ details;
315
324
  constructor(message, code, details) {
316
325
  super(message);
326
+ this.name = "BragduckError";
317
327
  this.code = code;
318
328
  this.details = details;
319
- this.name = "BragduckError";
320
329
  Error.captureStackTrace(this, this.constructor);
321
330
  }
322
331
  };
@@ -333,10 +342,11 @@ var init_errors = __esm({
333
342
  }
334
343
  };
335
344
  ApiError = class extends BragduckError {
345
+ statusCode;
336
346
  constructor(message, statusCode, details) {
337
347
  super(message, "API_ERROR", details);
338
- this.statusCode = statusCode;
339
348
  this.name = "ApiError";
349
+ this.statusCode = statusCode;
340
350
  }
341
351
  };
342
352
  NetworkError = class extends BragduckError {
@@ -439,7 +449,7 @@ async function findAvailablePort() {
439
449
  testServer.listen(port, "127.0.0.1");
440
450
  });
441
451
  return port;
442
- } catch (error) {
452
+ } catch {
443
453
  continue;
444
454
  }
445
455
  }
@@ -450,10 +460,10 @@ async function startOAuthCallbackServer(expectedState) {
450
460
  const timeout = OAUTH_CONFIG.TIMEOUT_MS;
451
461
  return new Promise((resolve, reject) => {
452
462
  let server = null;
453
- let timeoutId;
463
+ let timeoutId = null;
454
464
  const cleanup = () => {
455
465
  if (timeoutId) {
456
- clearTimeout(timeoutId);
466
+ globalThis.clearTimeout(timeoutId);
457
467
  }
458
468
  if (server) {
459
469
  if (typeof server.closeAllConnections === "function") {
@@ -499,7 +509,7 @@ async function startOAuthCallbackServer(expectedState) {
499
509
  }
500
510
  res.writeHead(200, { "Content-Type": "text/html" });
501
511
  res.end(SUCCESS_HTML);
502
- setTimeout(() => {
512
+ globalThis.setTimeout(() => {
503
513
  cleanup();
504
514
  resolve({
505
515
  code: String(code),
@@ -519,9 +529,11 @@ async function startOAuthCallbackServer(expectedState) {
519
529
  reject(new OAuthError(`OAuth server error: ${error.message}`));
520
530
  });
521
531
  server.listen(port, "127.0.0.1", () => {
522
- logger.debug(`OAuth callback server listening on http://127.0.0.1:${port}${OAUTH_CONFIG.CALLBACK_PATH}`);
532
+ logger.debug(
533
+ `OAuth callback server listening on http://127.0.0.1:${port}${OAUTH_CONFIG.CALLBACK_PATH}`
534
+ );
523
535
  });
524
- timeoutId = setTimeout(() => {
536
+ timeoutId = globalThis.setTimeout(() => {
525
537
  logger.debug("OAuth callback timeout");
526
538
  cleanup();
527
539
  reject(new OAuthError("Authentication timeout - no callback received within 2 minutes"));
@@ -680,10 +692,8 @@ async function openBrowser(url) {
680
692
  logger.debug("Browser opened successfully");
681
693
  } catch (error) {
682
694
  logger.debug(`Failed to open browser: ${error}`);
683
- throw new Error(
684
- `Failed to open browser automatically. Please open this URL manually:
685
- ${url}`
686
- );
695
+ throw new Error(`Failed to open browser automatically. Please open this URL manually:
696
+ ${url}`);
687
697
  }
688
698
  }
689
699
  var execAsync;
@@ -699,7 +709,7 @@ var init_browser = __esm({
699
709
  // src/services/auth.service.ts
700
710
  import { randomBytes as randomBytes2 } from "crypto";
701
711
  import { readFileSync as readFileSync2 } from "fs";
702
- import { fileURLToPath as fileURLToPath3 } from "url";
712
+ import { fileURLToPath as fileURLToPath3, URLSearchParams } from "url";
703
713
  import { dirname as dirname2, join as join3 } from "path";
704
714
  import { ofetch } from "ofetch";
705
715
  function getUserAgent() {
@@ -710,7 +720,7 @@ function getUserAgent() {
710
720
  const platform = process.platform;
711
721
  const arch = process.arch;
712
722
  return `BragDuck-CLI/${version2} (${platform}-${arch})`;
713
- } catch (error) {
723
+ } catch {
714
724
  logger.debug("Failed to read package.json version");
715
725
  return "BragDuck-CLI/2.0.0";
716
726
  }
@@ -808,7 +818,7 @@ var init_auth_service = __esm({
808
818
  const serverPromise = startOAuthCallbackServer(state);
809
819
  try {
810
820
  await openBrowser(authUrl);
811
- } catch (error) {
821
+ } catch {
812
822
  logger.warning("Could not open browser automatically");
813
823
  logger.info(`Please open this URL in your browser:`);
814
824
  logger.log(authUrl);
@@ -927,7 +937,7 @@ function getCurrentVersion() {
927
937
  const packageJsonPath2 = join5(__dirname4, "../../package.json");
928
938
  const packageJson2 = JSON.parse(readFileSync3(packageJsonPath2, "utf-8"));
929
939
  return packageJson2.version;
930
- } catch (error) {
940
+ } catch {
931
941
  logger.debug("Failed to read package.json version");
932
942
  return "1.0.0";
933
943
  }
@@ -1025,13 +1035,14 @@ var init_version = __esm({
1025
1035
  import { ofetch as ofetch2 } from "ofetch";
1026
1036
  import { readFileSync as readFileSync4 } from "fs";
1027
1037
  import { fileURLToPath as fileURLToPath5 } from "url";
1038
+ import { URLSearchParams as URLSearchParams2 } from "url";
1028
1039
  import { dirname as dirname4, join as join6 } from "path";
1029
1040
  function getCliVersion() {
1030
1041
  try {
1031
1042
  const packageJsonPath2 = join6(__dirname5, "../../package.json");
1032
1043
  const packageJson2 = JSON.parse(readFileSync4(packageJsonPath2, "utf-8"));
1033
1044
  return packageJson2.version;
1034
- } catch (error) {
1045
+ } catch {
1035
1046
  logger.debug("Failed to read package.json version");
1036
1047
  return "2.0.0";
1037
1048
  }
@@ -1061,29 +1072,30 @@ var init_api_service = __esm({
1061
1072
  baseURL: this.baseURL,
1062
1073
  // Request interceptor
1063
1074
  onRequest: async ({ options }) => {
1064
- logger.debug(`API Request: ${options.method} ${options.baseURL}${options.url}`);
1075
+ const extendedOptions = options;
1076
+ logger.debug(
1077
+ `API Request: ${options.method} ${extendedOptions.baseURL}${extendedOptions.url}`
1078
+ );
1065
1079
  const cliVersion = getCliVersion();
1066
1080
  const platform = getPlatformInfo();
1067
1081
  const userAgent = `BragDuck-CLI/${cliVersion} (${platform})`;
1068
1082
  const token = await authService.getAccessToken();
1083
+ const newHeaders = {
1084
+ "User-Agent": userAgent
1085
+ };
1086
+ if (options.headers) {
1087
+ const existingHeaders = new Headers(options.headers);
1088
+ existingHeaders.forEach((value, key) => {
1089
+ newHeaders[key] = value;
1090
+ });
1091
+ }
1069
1092
  if (token) {
1070
- options.headers = {
1071
- ...options.headers,
1072
- "User-Agent": userAgent,
1073
- Authorization: `Bearer ${token}`
1074
- };
1075
- } else {
1076
- options.headers = {
1077
- ...options.headers,
1078
- "User-Agent": userAgent
1079
- };
1093
+ newHeaders.Authorization = `Bearer ${token}`;
1080
1094
  }
1081
1095
  if (options.method && ["POST", "PUT", "PATCH"].includes(options.method)) {
1082
- options.headers = {
1083
- ...options.headers,
1084
- "Content-Type": "application/json"
1085
- };
1096
+ newHeaders["Content-Type"] = "application/json";
1086
1097
  }
1098
+ options.headers = newHeaders;
1087
1099
  },
1088
1100
  // Response interceptor for success
1089
1101
  onResponse: ({ response }) => {
@@ -1092,7 +1104,8 @@ var init_api_service = __esm({
1092
1104
  // Response interceptor for errors
1093
1105
  onResponseError: async ({ response, options }) => {
1094
1106
  const status = response.status;
1095
- const url = `${options.baseURL}${options.url}`;
1107
+ const extendedOptions = options;
1108
+ const url = `${extendedOptions.baseURL}${extendedOptions.url}`;
1096
1109
  logger.debug(`API Error: ${status} ${response.statusText} - ${url}`);
1097
1110
  if (status === HTTP_STATUS.UNAUTHORIZED) {
1098
1111
  logger.debug("Token expired, attempting refresh");
@@ -1104,7 +1117,9 @@ var init_api_service = __esm({
1104
1117
  if (error.message === "RETRY_WITH_NEW_TOKEN") {
1105
1118
  throw error;
1106
1119
  }
1107
- throw new TokenExpiredError('Your session has expired. Please run "bragduck init" to login again.');
1120
+ throw new TokenExpiredError(
1121
+ 'Your session has expired. Please run "bragduck init" to login again.'
1122
+ );
1108
1123
  }
1109
1124
  }
1110
1125
  let errorMessage = "An unexpected error occurred";
@@ -1150,23 +1165,39 @@ var init_api_service = __esm({
1150
1165
  }
1151
1166
  }
1152
1167
  /**
1153
- * Refine commits using AI
1168
+ * Refine brags using AI
1169
+ */
1170
+ async refineBrags(request) {
1171
+ logger.debug(`Refining ${request.brags.length} brags`);
1172
+ try {
1173
+ const response = await this.makeRequest(API_ENDPOINTS.BRAGS.REFINE, {
1174
+ method: "POST",
1175
+ body: request
1176
+ });
1177
+ logger.debug(`Successfully refined ${response.refined_brags.length} brags`);
1178
+ return response;
1179
+ } catch (_error) {
1180
+ logger.debug("Failed to refine brags");
1181
+ throw _error;
1182
+ }
1183
+ }
1184
+ /**
1185
+ * @deprecated Use refineBrags() instead. Will be removed in v3.0.0
1186
+ * Refine commits using AI (legacy endpoint)
1154
1187
  */
1155
1188
  async refineCommits(request) {
1189
+ logger.debug(`[DEPRECATED] Using legacy refineCommits - migrate to refineBrags`);
1156
1190
  logger.debug(`Refining ${request.commits.length} commits`);
1157
1191
  try {
1158
- const response = await this.makeRequest(
1159
- API_ENDPOINTS.COMMITS.REFINE,
1160
- {
1161
- method: "POST",
1162
- body: request
1163
- }
1164
- );
1192
+ const response = await this.makeRequest(API_ENDPOINTS.COMMITS.REFINE, {
1193
+ method: "POST",
1194
+ body: request
1195
+ });
1165
1196
  logger.debug(`Successfully refined ${response.refined_commits.length} commits`);
1166
1197
  return response;
1167
- } catch (error) {
1198
+ } catch (_error) {
1168
1199
  logger.debug("Failed to refine commits");
1169
- throw error;
1200
+ throw _error;
1170
1201
  }
1171
1202
  }
1172
1203
  /**
@@ -1175,18 +1206,15 @@ var init_api_service = __esm({
1175
1206
  async createBrags(request) {
1176
1207
  logger.debug(`Creating ${request.brags.length} brags`);
1177
1208
  try {
1178
- const response = await this.makeRequest(
1179
- API_ENDPOINTS.BRAGS.CREATE,
1180
- {
1181
- method: "POST",
1182
- body: request
1183
- }
1184
- );
1209
+ const response = await this.makeRequest(API_ENDPOINTS.BRAGS.CREATE, {
1210
+ method: "POST",
1211
+ body: request
1212
+ });
1185
1213
  logger.debug(`Successfully created ${response.created} brags`);
1186
1214
  return response;
1187
- } catch (error) {
1215
+ } catch (_error) {
1188
1216
  logger.debug("Failed to create brags");
1189
- throw error;
1217
+ throw _error;
1190
1218
  }
1191
1219
  }
1192
1220
  /**
@@ -1196,7 +1224,7 @@ var init_api_service = __esm({
1196
1224
  const { limit = 50, offset = 0, tags, search } = params;
1197
1225
  logger.debug(`Listing brags: limit=${limit}, offset=${offset}`);
1198
1226
  try {
1199
- const queryParams = new URLSearchParams({
1227
+ const queryParams = new URLSearchParams2({
1200
1228
  limit: limit.toString(),
1201
1229
  offset: offset.toString()
1202
1230
  });
@@ -1210,10 +1238,33 @@ var init_api_service = __esm({
1210
1238
  const response = await this.makeRequest(url, {
1211
1239
  method: "GET"
1212
1240
  });
1213
- logger.debug(`Successfully fetched ${response.brags.length} brags (total: ${response.total})`);
1241
+ logger.debug(
1242
+ `Successfully fetched ${response.brags.length} brags (total: ${response.total})`
1243
+ );
1214
1244
  return response;
1215
- } catch (error) {
1245
+ } catch (_error) {
1216
1246
  logger.debug("Failed to list brags");
1247
+ throw _error;
1248
+ }
1249
+ }
1250
+ /**
1251
+ * Get user's subscription status
1252
+ */
1253
+ async getSubscriptionStatus() {
1254
+ logger.debug(`Fetching subscription status from ${API_ENDPOINTS.SUBSCRIPTION.STATUS}`);
1255
+ try {
1256
+ const response = await this.makeRequest(API_ENDPOINTS.SUBSCRIPTION.STATUS, {
1257
+ method: "GET"
1258
+ });
1259
+ logger.debug(`Subscription API response: ${JSON.stringify(response)}`);
1260
+ const subscriptionData = response.data;
1261
+ logger.debug(
1262
+ `Subscription tier: ${subscriptionData.tier}, status: ${subscriptionData.status}`
1263
+ );
1264
+ return subscriptionData;
1265
+ } catch (error) {
1266
+ logger.debug(`Failed to fetch subscription status: ${error}`);
1267
+ logger.debug(`Error details: ${JSON.stringify(error, null, 2)}`);
1217
1268
  throw error;
1218
1269
  }
1219
1270
  }
@@ -1223,15 +1274,12 @@ var init_api_service = __esm({
1223
1274
  async checkVersion() {
1224
1275
  logger.debug("Checking for CLI updates");
1225
1276
  try {
1226
- const response = await this.makeRequest(
1227
- API_ENDPOINTS.VERSION,
1228
- {
1229
- method: "GET"
1230
- }
1231
- );
1277
+ const response = await this.makeRequest(API_ENDPOINTS.VERSION, {
1278
+ method: "GET"
1279
+ });
1232
1280
  logger.debug(`Latest version: ${response.latest_version}`);
1233
1281
  return response;
1234
- } catch (error) {
1282
+ } catch {
1235
1283
  logger.debug("Failed to check version");
1236
1284
  const { version: version2 } = await Promise.resolve().then(() => (init_version(), version_exports));
1237
1285
  return {
@@ -1247,7 +1295,7 @@ var init_api_service = __esm({
1247
1295
  try {
1248
1296
  await this.checkVersion();
1249
1297
  return true;
1250
- } catch (error) {
1298
+ } catch {
1251
1299
  return false;
1252
1300
  }
1253
1301
  }
@@ -1310,7 +1358,7 @@ ${chalk2.dim("Run")} ${chalk2.cyan("bragduck logout")} ${chalk2.dim("to sign out
1310
1358
  )
1311
1359
  );
1312
1360
  logger.log("");
1313
- setTimeout(() => {
1361
+ globalThis.setTimeout(() => {
1314
1362
  process.exit(0);
1315
1363
  }, 100);
1316
1364
  return;
@@ -1339,7 +1387,7 @@ ${chalk2.dim("You can now use")} ${chalk2.cyan("bragduck scan")} ${chalk2.dim("t
1339
1387
  )
1340
1388
  );
1341
1389
  logger.log("");
1342
- setTimeout(() => {
1390
+ globalThis.setTimeout(() => {
1343
1391
  process.exit(0);
1344
1392
  }, 100);
1345
1393
  return;
@@ -1396,14 +1444,17 @@ async function logoutCommand() {
1396
1444
  const isAuthenticated = await authService.isAuthenticated();
1397
1445
  if (!isAuthenticated) {
1398
1446
  logger.log(
1399
- boxen2(`${chalk3.yellow("Not currently authenticated")}
1447
+ boxen2(
1448
+ `${chalk3.yellow("Not currently authenticated")}
1400
1449
 
1401
- ${chalk3.dim("Nothing to logout from")}`, {
1402
- padding: 1,
1403
- margin: 1,
1404
- borderStyle: "round",
1405
- borderColor: "yellow"
1406
- })
1450
+ ${chalk3.dim("Nothing to logout from")}`,
1451
+ {
1452
+ padding: 1,
1453
+ margin: 1,
1454
+ borderStyle: "round",
1455
+ borderColor: "yellow"
1456
+ }
1457
+ )
1407
1458
  );
1408
1459
  return;
1409
1460
  }
@@ -1438,7 +1489,7 @@ ${chalk3.dim("Run")} ${chalk3.cyan("bragduck init")} ${chalk3.dim("to login agai
1438
1489
  }
1439
1490
  )
1440
1491
  );
1441
- } catch (error) {
1492
+ } catch {
1442
1493
  spinner.fail("Logout failed");
1443
1494
  logger.error("Failed to logout. Please try again.");
1444
1495
  process.exit(1);
@@ -1447,8 +1498,14 @@ ${chalk3.dim("Run")} ${chalk3.cyan("bragduck init")} ${chalk3.dim("to login agai
1447
1498
 
1448
1499
  // src/commands/scan.ts
1449
1500
  init_esm_shims();
1450
- import boxen4 from "boxen";
1451
- import chalk7 from "chalk";
1501
+ import boxen6 from "boxen";
1502
+
1503
+ // node_modules/@inquirer/core/dist/esm/lib/errors.mjs
1504
+ init_esm_shims();
1505
+ var CancelPromptError = class extends Error {
1506
+ name = "CancelPromptError";
1507
+ message = "Prompt was canceled";
1508
+ };
1452
1509
 
1453
1510
  // src/services/github.service.ts
1454
1511
  init_esm_shims();
@@ -1587,7 +1644,9 @@ var GitService = class {
1587
1644
  insertions: diffSummary.insertions,
1588
1645
  deletions: diffSummary.deletions
1589
1646
  };
1590
- logger.debug(`Commit ${sha}: ${stats.filesChanged} files, +${stats.insertions} -${stats.deletions}`);
1647
+ logger.debug(
1648
+ `Commit ${sha}: ${stats.filesChanged} files, +${stats.insertions} -${stats.deletions}`
1649
+ );
1591
1650
  return stats;
1592
1651
  } catch (error) {
1593
1652
  if (error.message && error.message.includes("unknown revision")) {
@@ -1627,7 +1686,7 @@ var GitService = class {
1627
1686
  ...commit,
1628
1687
  diffStats: stats
1629
1688
  };
1630
- } catch (error) {
1689
+ } catch {
1631
1690
  logger.debug(`Failed to get stats for commit ${commit.sha}, continuing without stats`);
1632
1691
  return commit;
1633
1692
  }
@@ -1642,7 +1701,7 @@ var GitService = class {
1642
1701
  try {
1643
1702
  const email = await this.git.raw(["config", "user.email"]);
1644
1703
  return email.trim() || null;
1645
- } catch (error) {
1704
+ } catch {
1646
1705
  logger.debug("Failed to get git user email");
1647
1706
  return null;
1648
1707
  }
@@ -1654,7 +1713,7 @@ var GitService = class {
1654
1713
  try {
1655
1714
  const name = await this.git.raw(["config", "user.name"]);
1656
1715
  return name.trim() || null;
1657
- } catch (error) {
1716
+ } catch {
1658
1717
  logger.debug("Failed to get git user name");
1659
1718
  return null;
1660
1719
  }
@@ -1689,7 +1748,7 @@ var GitHubService = class {
1689
1748
  try {
1690
1749
  await execAsync2("command gh --version");
1691
1750
  return true;
1692
- } catch (_error) {
1751
+ } catch {
1693
1752
  return false;
1694
1753
  }
1695
1754
  }
@@ -1711,7 +1770,7 @@ var GitHubService = class {
1711
1770
  try {
1712
1771
  await execAsync2("command gh auth status");
1713
1772
  return true;
1714
- } catch (_error) {
1773
+ } catch {
1715
1774
  return false;
1716
1775
  }
1717
1776
  }
@@ -1793,7 +1852,7 @@ var GitHubService = class {
1793
1852
  try {
1794
1853
  const { stdout } = await execAsync2("command gh api user --jq .login");
1795
1854
  return stdout.trim() || null;
1796
- } catch (_error) {
1855
+ } catch {
1797
1856
  logger.debug("Failed to get GitHub user");
1798
1857
  return null;
1799
1858
  }
@@ -1876,68 +1935,260 @@ var githubService = new GitHubService();
1876
1935
 
1877
1936
  // src/commands/scan.ts
1878
1937
  init_api_service();
1879
- init_auth_service();
1880
1938
  init_storage_service();
1881
1939
  init_logger();
1940
+ init_browser();
1941
+
1942
+ // src/utils/auth-helper.ts
1943
+ init_esm_shims();
1944
+ init_auth_service();
1945
+ import boxen5 from "boxen";
1946
+ init_logger();
1882
1947
 
1883
1948
  // src/ui/prompts.ts
1884
1949
  init_esm_shims();
1885
- import { checkbox, confirm as confirm2, input, select } from "@inquirer/prompts";
1950
+ import { checkbox, confirm as confirm2, input, select, editor } from "@inquirer/prompts";
1951
+ import boxen4 from "boxen";
1886
1952
 
1887
1953
  // src/ui/formatters.ts
1888
1954
  init_esm_shims();
1889
- import chalk5 from "chalk";
1890
1955
  import Table from "cli-table3";
1891
1956
  import terminalLink from "terminal-link";
1957
+
1958
+ // src/ui/theme.ts
1959
+ init_esm_shims();
1960
+ import chalk5 from "chalk";
1961
+ var colors = {
1962
+ // Primary colors for main actions and interactive elements
1963
+ primary: chalk5.cyan,
1964
+ // Success states
1965
+ success: chalk5.green,
1966
+ successBold: chalk5.green.bold,
1967
+ // Warning states
1968
+ warning: chalk5.yellow,
1969
+ warningBold: chalk5.yellow.bold,
1970
+ // Error states
1971
+ error: chalk5.red,
1972
+ errorBold: chalk5.red.bold,
1973
+ // Info and metadata
1974
+ info: chalk5.gray,
1975
+ infoDim: chalk5.dim,
1976
+ // Highlighted/important data
1977
+ highlight: chalk5.yellow.bold,
1978
+ highlightCyan: chalk5.cyan.bold,
1979
+ // Text emphasis
1980
+ bold: chalk5.bold,
1981
+ dim: chalk5.dim,
1982
+ // Special purpose
1983
+ white: chalk5.white,
1984
+ gray: chalk5.gray
1985
+ };
1986
+ var theme = {
1987
+ /**
1988
+ * Format command names and CLI actions
1989
+ */
1990
+ command: (text) => colors.primary(text),
1991
+ /**
1992
+ * Format success messages
1993
+ */
1994
+ success: (text) => colors.success(`\u2713 ${text}`),
1995
+ successBold: (text) => colors.successBold(`\u2713 ${text}`),
1996
+ /**
1997
+ * Format error messages
1998
+ */
1999
+ error: (text) => colors.error(`\u2717 ${text}`),
2000
+ errorBold: (text) => colors.errorBold(`\u2717 ${text}`),
2001
+ /**
2002
+ * Format warning messages
2003
+ */
2004
+ warning: (text) => colors.warning(`\u26A0 ${text}`),
2005
+ /**
2006
+ * Format info messages
2007
+ */
2008
+ info: (text) => colors.info(text),
2009
+ /**
2010
+ * Format labels (e.g., "User:", "Email:")
2011
+ */
2012
+ label: (text) => colors.gray(`${text}:`),
2013
+ /**
2014
+ * Format values
2015
+ */
2016
+ value: (text) => colors.highlight(text),
2017
+ /**
2018
+ * Format counts and numbers
2019
+ */
2020
+ count: (num) => colors.highlightCyan(num.toString()),
2021
+ /**
2022
+ * Format file paths
2023
+ */
2024
+ path: (text) => colors.info(text),
2025
+ /**
2026
+ * Format step indicators (e.g., "Step 1/5:")
2027
+ */
2028
+ step: (current, total) => colors.primary(`[${current}/${total}]`),
2029
+ /**
2030
+ * Format progress text
2031
+ */
2032
+ progress: (text) => colors.infoDim(text),
2033
+ /**
2034
+ * Format emphasized text
2035
+ */
2036
+ emphasis: (text) => colors.bold(text),
2037
+ /**
2038
+ * Format de-emphasized/secondary text
2039
+ */
2040
+ secondary: (text) => colors.dim(text),
2041
+ /**
2042
+ * Format interactive elements (prompts, selections)
2043
+ */
2044
+ interactive: (text) => colors.primary(text),
2045
+ /**
2046
+ * Format keys in key-value pairs
2047
+ */
2048
+ key: (text) => colors.white(text)
2049
+ };
2050
+ var boxStyles = {
2051
+ success: {
2052
+ borderColor: "green",
2053
+ borderStyle: "round",
2054
+ padding: 1,
2055
+ margin: 1
2056
+ },
2057
+ error: {
2058
+ borderColor: "red",
2059
+ borderStyle: "round",
2060
+ padding: 1,
2061
+ margin: 1
2062
+ },
2063
+ warning: {
2064
+ borderColor: "yellow",
2065
+ borderStyle: "round",
2066
+ padding: 1,
2067
+ margin: 1
2068
+ },
2069
+ info: {
2070
+ borderColor: "cyan",
2071
+ borderStyle: "round",
2072
+ padding: 1,
2073
+ margin: 1
2074
+ },
2075
+ plain: {
2076
+ borderColor: "gray",
2077
+ borderStyle: "round",
2078
+ padding: 1,
2079
+ margin: 1
2080
+ }
2081
+ };
2082
+ var tableStyles = {
2083
+ default: {
2084
+ style: {
2085
+ head: [],
2086
+ border: ["gray"]
2087
+ }
2088
+ },
2089
+ compact: {
2090
+ style: {
2091
+ head: [],
2092
+ border: ["dim"],
2093
+ compact: true
2094
+ }
2095
+ }
2096
+ };
2097
+ var sizeIndicators = {
2098
+ small: colors.success("\u25CF"),
2099
+ medium: colors.warning("\u25CF"),
2100
+ large: colors.error("\u25CF")
2101
+ };
2102
+ function getSizeIndicator(insertions, deletions) {
2103
+ const total = insertions + deletions;
2104
+ if (total < 50) {
2105
+ return `${sizeIndicators.small} Small`;
2106
+ } else if (total < 200) {
2107
+ return `${sizeIndicators.medium} Medium`;
2108
+ } else {
2109
+ return `${sizeIndicators.large} Large`;
2110
+ }
2111
+ }
2112
+ function formatDiffStats(insertions, deletions) {
2113
+ return `${colors.success(`+${insertions}`)} ${colors.error(`-${deletions}`)}`;
2114
+ }
2115
+
2116
+ // src/ui/formatters.ts
1892
2117
  function formatCommitChoice(commit) {
1893
2118
  let displaySha;
1894
2119
  if (commit.sha.startsWith("pr-")) {
1895
2120
  const prNumber = commit.sha.replace("pr-", "#");
1896
- displaySha = chalk5.yellow(prNumber);
2121
+ displaySha = colors.highlight(prNumber);
1897
2122
  } else {
1898
- displaySha = chalk5.yellow(commit.sha.substring(0, 7));
2123
+ displaySha = colors.highlight(commit.sha.substring(0, 7));
2124
+ }
2125
+ const lines = commit.message.split("\n");
2126
+ const title = lines[0];
2127
+ let bodyPreview = "";
2128
+ if (lines.length > 1) {
2129
+ const body = lines.slice(1).join(" ").trim();
2130
+ if (body.length > 0) {
2131
+ const preview = body.length > 80 ? body.substring(0, 80) + "..." : body;
2132
+ bodyPreview = `
2133
+ ${colors.infoDim(preview)}`;
2134
+ }
1899
2135
  }
1900
- const message = commit.message.split("\n")[0];
1901
- const author = chalk5.gray(`by ${commit.author}`);
1902
- const date = chalk5.gray(new Date(commit.date).toLocaleDateString());
2136
+ const author = colors.info(`by ${commit.author}`);
2137
+ const date = colors.info(new Date(commit.date).toLocaleDateString());
1903
2138
  let stats = "";
2139
+ let sizeIndicator = "";
1904
2140
  if (commit.diffStats) {
1905
2141
  const { filesChanged, insertions, deletions } = commit.diffStats;
1906
- stats = chalk5.gray(
1907
- ` [${filesChanged} files, ${chalk5.green(`+${insertions}`)} ${chalk5.red(`-${deletions}`)}]`
1908
- );
2142
+ sizeIndicator = ` ${getSizeIndicator(insertions, deletions)}`;
2143
+ stats = colors.info(` [${filesChanged} files, ${formatDiffStats(insertions, deletions)}]`);
1909
2144
  }
1910
- return `${displaySha} ${message}${stats}
1911
- ${author} \u2022 ${date}`;
2145
+ return `${displaySha} ${title}${sizeIndicator}${stats}
2146
+ ${author} \u2022 ${date}${bodyPreview}`;
1912
2147
  }
1913
- function formatRefinedCommitsTable(commits) {
2148
+ function formatRefinedCommitsTable(brags, selectedCommits) {
1914
2149
  const table = new Table({
1915
2150
  head: [
1916
- chalk5.cyan("SHA"),
1917
- chalk5.cyan("Original"),
1918
- chalk5.cyan("Refined Title"),
1919
- chalk5.cyan("Description"),
1920
- chalk5.cyan("Tags")
2151
+ colors.primary("SHA"),
2152
+ colors.primary("Original"),
2153
+ colors.primary("Refined Title"),
2154
+ colors.primary("Description"),
2155
+ colors.primary("Tags")
1921
2156
  ],
1922
- colWidths: [10, 30, 30, 40, 20],
2157
+ colWidths: [12, 28, 30, 40, 20],
1923
2158
  wordWrap: true,
1924
- style: {
1925
- head: [],
1926
- border: ["gray"]
1927
- }
2159
+ style: tableStyles.default.style
1928
2160
  });
1929
- commits.forEach((commit) => {
1930
- const sha = commit.sha.substring(0, 7);
1931
- const original = commit.original_message.split("\n")[0] || "";
1932
- const title = commit.refined_title;
1933
- const description = commit.refined_description.substring(0, 100) + "...";
1934
- const tags = (commit.suggested_tags || []).join(", ") || "none";
2161
+ brags.forEach((brag, index) => {
2162
+ const isNewBragType = !("sha" in brag);
2163
+ let displaySha;
2164
+ if (isNewBragType && selectedCommits) {
2165
+ const commit = selectedCommits[index];
2166
+ if (commit.sha.startsWith("pr-")) {
2167
+ displaySha = commit.sha.replace("pr-", "#");
2168
+ } else {
2169
+ displaySha = commit.sha.substring(0, 7);
2170
+ }
2171
+ } else if (!isNewBragType) {
2172
+ const commit = brag;
2173
+ if (commit.sha.startsWith("pr-")) {
2174
+ displaySha = commit.sha.replace("pr-", "#");
2175
+ } else {
2176
+ displaySha = commit.sha.substring(0, 7);
2177
+ }
2178
+ } else {
2179
+ displaySha = `#${index + 1}`;
2180
+ }
2181
+ const original = isNewBragType ? typeof brag.original_input === "string" ? brag.original_input.split("\n")[0] || "" : "Raw input" : brag.original_message.split("\n")[0] || "";
2182
+ const title = brag.refined_title;
2183
+ const description = brag.refined_description.length > 100 ? brag.refined_description.substring(0, 97) + "..." : brag.refined_description;
2184
+ const tags = (brag.suggested_tags || []).join(", ") || "none";
2185
+ const rowNum = colors.infoDim(`${index + 1}.`);
1935
2186
  table.push([
1936
- chalk5.yellow(sha),
1937
- chalk5.gray(original),
1938
- chalk5.white(title),
1939
- chalk5.dim(description),
1940
- chalk5.blue(tags)
2187
+ `${rowNum} ${colors.highlight(displaySha)}`,
2188
+ colors.info(original),
2189
+ colors.white(title),
2190
+ colors.dim(description),
2191
+ colors.primary(tags)
1941
2192
  ]);
1942
2193
  });
1943
2194
  return table.toString();
@@ -1946,26 +2197,35 @@ function formatCommitStats(commits) {
1946
2197
  const totalFiles = commits.reduce((sum, c) => sum + (c.diffStats?.filesChanged || 0), 0);
1947
2198
  const totalInsertions = commits.reduce((sum, c) => sum + (c.diffStats?.insertions || 0), 0);
1948
2199
  const totalDeletions = commits.reduce((sum, c) => sum + (c.diffStats?.deletions || 0), 0);
1949
- return chalk5.gray("Total changes: ") + chalk5.white(`${totalFiles} files`) + chalk5.gray(", ") + chalk5.green(`+${totalInsertions}`) + chalk5.gray(" ") + chalk5.red(`-${totalDeletions}`);
2200
+ return colors.info("Total changes: ") + colors.white(`${totalFiles} files`) + colors.info(", ") + formatDiffStats(totalInsertions, totalDeletions);
2201
+ }
2202
+ function formatSelectionSummary(count, commits) {
2203
+ const totalFiles = commits.reduce((sum, c) => sum + (c.diffStats?.filesChanged || 0), 0);
2204
+ const totalInsertions = commits.reduce((sum, c) => sum + (c.diffStats?.insertions || 0), 0);
2205
+ const totalDeletions = commits.reduce((sum, c) => sum + (c.diffStats?.deletions || 0), 0);
2206
+ const selectedText = theme.success(`Selected ${theme.count(count)} PR${count > 1 ? "s" : ""}`);
2207
+ const stats = `${theme.count(totalFiles)} files changed, ${formatDiffStats(totalInsertions, totalDeletions)}`;
2208
+ return `
2209
+ ${selectedText}: ${stats}`;
1950
2210
  }
1951
2211
  function formatSuccessMessage(count) {
1952
2212
  const emoji = "\u{1F389}";
1953
- const title = chalk5.green.bold(
2213
+ const title = colors.successBold(
1954
2214
  `${emoji} Successfully created ${count} brag${count > 1 ? "s" : ""}!`
1955
2215
  );
1956
- const message = chalk5.white("\nYour achievements are now saved and ready to showcase.");
1957
- const hint = chalk5.dim("\nRun ") + chalk5.cyan("bragduck list") + chalk5.dim(" to see all your brags");
2216
+ const message = colors.white("\nYour achievements are now saved and ready to showcase.");
2217
+ const hint = theme.secondary("\nRun ") + theme.command("bragduck list") + theme.secondary(" to see all your brags");
1958
2218
  const url = "https://bragduck.com/app/brags";
1959
2219
  const clickableUrl = terminalLink(url, url, {
1960
- fallback: () => chalk5.blue.underline(url)
2220
+ fallback: () => colors.primary(url)
1961
2221
  });
1962
- const webUrl = chalk5.dim("\n\nOr, check ") + clickableUrl + chalk5.dim(" to see all your brags");
2222
+ const webUrl = theme.secondary("\n\nOr, check ") + clickableUrl + theme.secondary(" to see all your brags");
1963
2223
  return `${title}${message}${hint}${webUrl}`;
1964
2224
  }
1965
2225
  function formatErrorMessage(message, hint) {
1966
- const title = chalk5.red.bold("\u2717 Error");
1967
- const error = chalk5.white(message);
1968
- const hintText = hint ? chalk5.dim("\n\nHint: ") + chalk5.cyan(hint) : "";
2226
+ const title = colors.errorBold("\u2717 Error");
2227
+ const error = colors.white(message);
2228
+ const hintText = hint ? theme.secondary("\n\nHint: ") + theme.command(hint) : "";
1969
2229
  return `${title}
1970
2230
 
1971
2231
  ${error}${hintText}`;
@@ -2024,11 +2284,128 @@ async function promptDaysToScan(defaultDays = 30) {
2024
2284
  }
2025
2285
  return parseInt(selected, 10);
2026
2286
  }
2287
+ async function promptSortOption() {
2288
+ const choices = [
2289
+ {
2290
+ name: "By date (newest first)",
2291
+ value: "date",
2292
+ description: "Most recent PRs first"
2293
+ },
2294
+ { name: "By size (largest first)", value: "size", description: "Most lines changed" },
2295
+ { name: "By files (most files)", value: "files", description: "Most files changed" },
2296
+ { name: "No sorting", value: "none", description: "Keep original order" }
2297
+ ];
2298
+ return await select({
2299
+ message: "How would you like to sort the PRs?",
2300
+ choices,
2301
+ default: "date"
2302
+ });
2303
+ }
2304
+ async function promptReviewBrags(refinedCommits) {
2305
+ const acceptedBrags = [];
2306
+ console.log("\n" + theme.info("Review each brag before creation:") + "\n");
2307
+ for (let i = 0; i < refinedCommits.length; i++) {
2308
+ const commit = refinedCommits[i];
2309
+ const current = i + 1;
2310
+ const total = refinedCommits.length;
2311
+ const displaySha = commit.sha.startsWith("pr-") ? commit.sha.replace("pr-", "#") : commit.sha.substring(0, 7);
2312
+ const bragDetails = `${theme.step(current, total)} ${colors.highlight(displaySha)}
2313
+
2314
+ ${theme.label("Title")} ${colors.white(commit.refined_title)}
2315
+
2316
+ ${theme.label("Description")}
2317
+ ${colors.white(commit.refined_description)}
2318
+
2319
+ ${theme.label("Tags")} ${colors.primary((commit.suggested_tags || []).join(", ") || "none")}
2320
+
2321
+ ${theme.label("Impact Score")} ${colors.highlight(commit.impact_score?.toString() || "N/A")}`;
2322
+ console.log(boxen4(bragDetails, boxStyles.info));
2323
+ console.log("");
2324
+ const action = await select({
2325
+ message: `What would you like to do with this brag?`,
2326
+ choices: [
2327
+ { name: "\u2713 Accept", value: "accept", description: "Add this brag as-is" },
2328
+ { name: "\u270E Edit title", value: "edit-title", description: "Modify the title" },
2329
+ { name: "\u270E Edit description", value: "edit-desc", description: "Modify the description" },
2330
+ { name: "\u270E Edit both", value: "edit-both", description: "Modify title and description" },
2331
+ { name: "\u2717 Skip", value: "skip", description: "Skip this brag" },
2332
+ ...current < total ? [
2333
+ {
2334
+ name: "\u2713 Accept all remaining",
2335
+ value: "accept-all",
2336
+ description: "Accept this and all remaining brags"
2337
+ }
2338
+ ] : [],
2339
+ { name: "\u2717 Cancel", value: "cancel", description: "Cancel and discard all brags" }
2340
+ ]
2341
+ });
2342
+ if (action === "cancel") {
2343
+ return [];
2344
+ }
2345
+ if (action === "accept-all") {
2346
+ acceptedBrags.push(commit);
2347
+ for (let j = i + 1; j < refinedCommits.length; j++) {
2348
+ acceptedBrags.push(refinedCommits[j]);
2349
+ }
2350
+ break;
2351
+ }
2352
+ if (action === "skip") {
2353
+ continue;
2354
+ }
2355
+ let editedCommit = { ...commit };
2356
+ if (action === "edit-title" || action === "edit-both") {
2357
+ const newTitle = await input({
2358
+ message: "Enter new title:",
2359
+ default: commit.refined_title
2360
+ });
2361
+ editedCommit.refined_title = newTitle;
2362
+ }
2363
+ if (action === "edit-desc" || action === "edit-both") {
2364
+ const newDesc = await editor({
2365
+ message: "Edit description (will open your default editor):",
2366
+ default: commit.refined_description
2367
+ });
2368
+ editedCommit.refined_description = newDesc;
2369
+ }
2370
+ acceptedBrags.push(editedCommit);
2371
+ }
2372
+ return acceptedBrags;
2373
+ }
2374
+
2375
+ // src/utils/auth-helper.ts
2376
+ async function ensureAuthenticated() {
2377
+ const isAuthenticated = await authService.isAuthenticated();
2378
+ if (isAuthenticated) {
2379
+ return true;
2380
+ }
2381
+ logger.log("");
2382
+ logger.log(
2383
+ boxen5(
2384
+ theme.warning("Not authenticated") + "\n\nYou need to be logged in to use this command.\n\nWould you like to authenticate now?",
2385
+ boxStyles.warning
2386
+ )
2387
+ );
2388
+ logger.log("");
2389
+ const shouldAuth = await promptConfirm("Authenticate now?", true);
2390
+ if (!shouldAuth) {
2391
+ logger.log("");
2392
+ logger.info(
2393
+ theme.secondary("Authentication skipped. Run ") + theme.command("bragduck init") + theme.secondary(" when you're ready to authenticate.")
2394
+ );
2395
+ logger.log("");
2396
+ return false;
2397
+ }
2398
+ try {
2399
+ await initCommand();
2400
+ return true;
2401
+ } catch {
2402
+ return false;
2403
+ }
2404
+ }
2027
2405
 
2028
2406
  // src/ui/spinners.ts
2029
2407
  init_esm_shims();
2030
2408
  import ora3 from "ora";
2031
- import chalk6 from "chalk";
2032
2409
  function createSpinner(text) {
2033
2410
  return ora3({
2034
2411
  text,
@@ -2036,57 +2413,99 @@ function createSpinner(text) {
2036
2413
  spinner: "dots"
2037
2414
  });
2038
2415
  }
2039
- function fetchingPRsSpinner(days) {
2040
- return createSpinner(`Fetching merged PRs from the last ${days} days...`);
2041
- }
2042
- function refiningCommitsSpinner(count) {
2043
- return createSpinner(`Refining ${count} commit${count > 1 ? "s" : ""} with AI...`);
2044
- }
2045
- function creatingBragsSpinner(count) {
2046
- return createSpinner(`Creating ${count} brag${count > 1 ? "s" : ""}...`);
2416
+ function createStepSpinner(currentStep, totalSteps, text) {
2417
+ const stepIndicator = theme.step(currentStep, totalSteps);
2418
+ return ora3({
2419
+ text: `${stepIndicator} ${text}`,
2420
+ color: "cyan",
2421
+ spinner: "dots"
2422
+ });
2047
2423
  }
2048
2424
  function fetchingBragsSpinner() {
2049
2425
  return createSpinner("Fetching your brags...");
2050
2426
  }
2051
2427
  function succeedSpinner(spinner, text) {
2052
2428
  if (text) {
2053
- spinner.succeed(chalk6.green(text));
2429
+ spinner.succeed(colors.success(text));
2054
2430
  } else {
2055
2431
  spinner.succeed();
2056
2432
  }
2057
2433
  }
2058
2434
  function failSpinner(spinner, text) {
2059
2435
  if (text) {
2060
- spinner.fail(chalk6.red(text));
2436
+ spinner.fail(colors.error(text));
2061
2437
  } else {
2062
2438
  spinner.fail();
2063
2439
  }
2064
2440
  }
2441
+ function succeedStepSpinner(spinner, currentStep, totalSteps, text) {
2442
+ const stepIndicator = theme.step(currentStep, totalSteps);
2443
+ spinner.succeed(`${stepIndicator} ${colors.success(text)}`);
2444
+ }
2445
+ function failStepSpinner(spinner, currentStep, totalSteps, text) {
2446
+ const stepIndicator = theme.step(currentStep, totalSteps);
2447
+ spinner.fail(`${stepIndicator} ${colors.error(text)}`);
2448
+ }
2065
2449
 
2066
2450
  // src/commands/scan.ts
2067
2451
  async function scanCommand(options = {}) {
2068
2452
  logger.log("");
2453
+ const TOTAL_STEPS = 5;
2069
2454
  try {
2070
- const isAuthenticated = await authService.isAuthenticated();
2455
+ const isAuthenticated = await ensureAuthenticated();
2071
2456
  if (!isAuthenticated) {
2457
+ process.exit(1);
2458
+ }
2459
+ logger.debug("Fetching subscription status...");
2460
+ const subscriptionStatus = await apiService.getSubscriptionStatus();
2461
+ logger.debug("Subscription status response:", JSON.stringify(subscriptionStatus, null, 2));
2462
+ logger.debug(
2463
+ `Checking tier: "${subscriptionStatus.tier}" (type: ${typeof subscriptionStatus.tier})`
2464
+ );
2465
+ logger.debug(`Tier === 'FREE': ${subscriptionStatus.tier === "FREE"}`);
2466
+ if (subscriptionStatus.tier === "FREE") {
2467
+ logger.debug("FREE tier detected - blocking scan command");
2468
+ logger.log("");
2072
2469
  logger.log(
2073
- boxen4(
2074
- `${chalk7.yellow.bold("\u26A0 Not authenticated")}
2075
-
2076
- Please run ${chalk7.cyan("bragduck init")} to login first.`,
2470
+ boxen6(
2471
+ 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!"),
2077
2472
  {
2473
+ ...boxStyles.warning,
2078
2474
  padding: 1,
2079
- margin: 1,
2080
- borderStyle: "round",
2081
- borderColor: "yellow"
2475
+ margin: 1
2082
2476
  }
2083
2477
  )
2084
2478
  );
2085
- process.exit(1);
2479
+ logger.log("");
2480
+ const shouldOpenBrowser = await promptConfirm(
2481
+ "Open subscription plans in your browser?",
2482
+ true
2483
+ );
2484
+ if (shouldOpenBrowser) {
2485
+ logger.log("");
2486
+ const plansUrl = "https://bragduck.com/app/settings/plans";
2487
+ try {
2488
+ await openBrowser(plansUrl);
2489
+ logger.info(theme.secondary("Opening browser..."));
2490
+ } catch {
2491
+ logger.warning("Could not open browser automatically");
2492
+ logger.info(`Please visit: ${theme.value(plansUrl)}`);
2493
+ }
2494
+ }
2495
+ logger.log("");
2496
+ return;
2086
2497
  }
2498
+ logger.debug(`Subscription tier "${subscriptionStatus.tier}" - proceeding with scan`);
2499
+ const repoSpinner = createStepSpinner(1, TOTAL_STEPS, "Validating GitHub repository");
2500
+ repoSpinner.start();
2087
2501
  await githubService.validateGitHubRepository();
2088
2502
  const repoInfo = await githubService.getRepositoryInfo();
2089
- logger.info(`Repository: ${chalk7.cyan(repoInfo.fullName)} on GitHub`);
2503
+ succeedStepSpinner(
2504
+ repoSpinner,
2505
+ 1,
2506
+ TOTAL_STEPS,
2507
+ `Repository: ${theme.value(repoInfo.fullName)}`
2508
+ );
2090
2509
  logger.log("");
2091
2510
  let days = options.days;
2092
2511
  if (!days) {
@@ -2094,8 +2513,12 @@ Please run ${chalk7.cyan("bragduck init")} to login first.`,
2094
2513
  days = await promptDaysToScan(defaultDays);
2095
2514
  logger.log("");
2096
2515
  }
2097
- const spinner = fetchingPRsSpinner(days);
2098
- spinner.start();
2516
+ const prSpinner = createStepSpinner(
2517
+ 2,
2518
+ TOTAL_STEPS,
2519
+ `Fetching merged PRs from the last ${days} days`
2520
+ );
2521
+ prSpinner.start();
2099
2522
  let prs;
2100
2523
  if (options.all) {
2101
2524
  prs = await githubService.getMergedPRs({ days });
@@ -2104,93 +2527,119 @@ Please run ${chalk7.cyan("bragduck init")} to login first.`,
2104
2527
  }
2105
2528
  const commits = prs.map((pr) => githubService.transformPRToCommit(pr));
2106
2529
  if (commits.length === 0) {
2107
- failSpinner(spinner, `No merged PRs found in the last ${days} days`);
2530
+ failStepSpinner(prSpinner, 2, TOTAL_STEPS, `No merged PRs found in the last ${days} days`);
2108
2531
  logger.log("");
2109
2532
  logger.info("Try increasing the number of days or check your GitHub activity");
2110
2533
  return;
2111
2534
  }
2112
- succeedSpinner(spinner, `Found ${commits.length} PR${commits.length > 1 ? "s" : ""}`);
2535
+ succeedStepSpinner(
2536
+ prSpinner,
2537
+ 2,
2538
+ TOTAL_STEPS,
2539
+ `Found ${theme.count(commits.length)} PR${commits.length > 1 ? "s" : ""}`
2540
+ );
2113
2541
  logger.log("");
2114
2542
  logger.log(formatCommitStats(commits));
2115
2543
  logger.log("");
2116
- const selectedShas = await promptSelectCommits(commits);
2544
+ const sortOption = await promptSortOption();
2545
+ logger.log("");
2546
+ let sortedCommits = [...commits];
2547
+ if (sortOption === "date") {
2548
+ sortedCommits.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
2549
+ } else if (sortOption === "size") {
2550
+ sortedCommits.sort((a, b) => {
2551
+ const sizeA = (a.diffStats?.insertions || 0) + (a.diffStats?.deletions || 0);
2552
+ const sizeB = (b.diffStats?.insertions || 0) + (b.diffStats?.deletions || 0);
2553
+ return sizeB - sizeA;
2554
+ });
2555
+ } else if (sortOption === "files") {
2556
+ sortedCommits.sort((a, b) => {
2557
+ const filesA = a.diffStats?.filesChanged || 0;
2558
+ const filesB = b.diffStats?.filesChanged || 0;
2559
+ return filesB - filesA;
2560
+ });
2561
+ }
2562
+ const selectedShas = await promptSelectCommits(sortedCommits);
2117
2563
  if (selectedShas.length === 0) {
2118
- logger.warning("No commits selected");
2564
+ logger.log("");
2565
+ logger.info(theme.secondary("No PRs selected. Scan cancelled."));
2566
+ logger.log("");
2119
2567
  return;
2120
2568
  }
2121
- const selectedCommits = commits.filter((c) => selectedShas.includes(c.sha));
2122
- logger.log("");
2123
- logger.success(`Selected ${selectedCommits.length} PR${selectedCommits.length > 1 ? "s" : ""}`);
2569
+ const selectedCommits = sortedCommits.filter((c) => selectedShas.includes(c.sha));
2570
+ logger.log(formatSelectionSummary(selectedCommits.length, selectedCommits));
2124
2571
  logger.log("");
2125
- const refineSpinner = refiningCommitsSpinner(selectedCommits.length);
2572
+ const refineSpinner = createStepSpinner(
2573
+ 3,
2574
+ TOTAL_STEPS,
2575
+ `Refining ${theme.count(selectedCommits.length)} PR${selectedCommits.length > 1 ? "s" : ""} with AI`
2576
+ );
2126
2577
  refineSpinner.start();
2127
2578
  const refineRequest = {
2128
- commits: selectedCommits.map((c) => ({
2129
- sha: c.sha,
2130
- message: c.message,
2131
- author: c.author,
2579
+ brags: selectedCommits.map((c) => ({
2580
+ text: c.message,
2132
2581
  date: c.date,
2133
- diff_stats: c.diffStats ? {
2134
- files_changed: c.diffStats.filesChanged,
2135
- insertions: c.diffStats.insertions,
2136
- deletions: c.diffStats.deletions
2137
- } : void 0
2582
+ title: c.message.split("\n")[0]
2583
+ // First line as initial title
2138
2584
  }))
2139
2585
  };
2140
- const refineResponse = await apiService.refineCommits(refineRequest);
2141
- const refinedCommits = refineResponse.refined_commits;
2142
- succeedSpinner(refineSpinner, "PRs refined successfully");
2586
+ const refineResponse = await apiService.refineBrags(refineRequest);
2587
+ let refinedBrags = refineResponse.refined_brags;
2588
+ succeedStepSpinner(refineSpinner, 3, TOTAL_STEPS, "PRs refined successfully");
2143
2589
  logger.log("");
2144
2590
  logger.info("Preview of refined brags:");
2145
2591
  logger.log("");
2146
- logger.log(formatRefinedCommitsTable(refinedCommits));
2592
+ logger.log(formatRefinedCommitsTable(refinedBrags, selectedCommits));
2147
2593
  logger.log("");
2148
- const shouldCreate = await promptConfirm("Create these brags?", true);
2149
- if (!shouldCreate) {
2150
- logger.warning("Cancelled");
2594
+ const acceptedBrags = await promptReviewBrags(refinedBrags, selectedCommits);
2595
+ if (acceptedBrags.length === 0) {
2596
+ logger.log("");
2597
+ logger.info(theme.secondary("No brags selected for creation. Scan cancelled."));
2598
+ logger.log("");
2151
2599
  return;
2152
2600
  }
2153
2601
  logger.log("");
2154
- const createSpinner2 = creatingBragsSpinner(refinedCommits.length);
2602
+ const createSpinner2 = createStepSpinner(
2603
+ 4,
2604
+ TOTAL_STEPS,
2605
+ `Creating ${theme.count(acceptedBrags.length)} brag${acceptedBrags.length > 1 ? "s" : ""}`
2606
+ );
2155
2607
  createSpinner2.start();
2156
2608
  const createRequest = {
2157
- brags: refinedCommits.map((refined) => {
2158
- const originalCommit = selectedCommits.find((c) => c.sha === refined.sha);
2609
+ brags: acceptedBrags.map((refined, index) => {
2610
+ const originalCommit = selectedCommits[index];
2159
2611
  return {
2160
- commit_sha: refined.sha,
2612
+ commit_sha: originalCommit?.sha || `brag-${index}`,
2161
2613
  title: refined.refined_title,
2162
2614
  description: refined.refined_description,
2163
2615
  tags: refined.suggested_tags,
2164
2616
  repository: repoInfo.url,
2165
- date: originalCommit?.date || (/* @__PURE__ */ new Date()).toISOString(),
2166
- commit_url: refined.commit_url,
2167
- impact_score: refined.impact_score,
2617
+ date: refined.date,
2618
+ commit_url: originalCommit?.url || "",
2619
+ impact_score: refined.suggested_impactLevel,
2168
2620
  impact_description: refined.impact_description
2169
2621
  };
2170
2622
  })
2171
2623
  };
2172
2624
  const createResponse = await apiService.createBrags(createRequest);
2173
- succeedSpinner(createSpinner2, `Created ${createResponse.created} brags`);
2174
- logger.log("");
2175
- logger.log(
2176
- boxen4(formatSuccessMessage(createResponse.created), {
2177
- padding: 1,
2178
- margin: 1,
2179
- borderStyle: "round",
2180
- borderColor: "green"
2181
- })
2625
+ succeedStepSpinner(
2626
+ createSpinner2,
2627
+ 4,
2628
+ TOTAL_STEPS,
2629
+ `Created ${theme.count(createResponse.created)} brag${createResponse.created > 1 ? "s" : ""}`
2182
2630
  );
2631
+ logger.log("");
2632
+ logger.log(boxen6(formatSuccessMessage(createResponse.created), boxStyles.success));
2183
2633
  } catch (error) {
2634
+ if (error instanceof CancelPromptError) {
2635
+ logger.log("");
2636
+ logger.info(theme.secondary("Scan cancelled."));
2637
+ logger.log("");
2638
+ return;
2639
+ }
2184
2640
  const err = error;
2185
2641
  logger.log("");
2186
- logger.log(
2187
- boxen4(formatErrorMessage(err.message, getErrorHint2(err)), {
2188
- padding: 1,
2189
- margin: 1,
2190
- borderStyle: "round",
2191
- borderColor: "red"
2192
- })
2193
- );
2642
+ logger.log(boxen6(formatErrorMessage(err.message, getErrorHint2(err)), boxStyles.error));
2194
2643
  process.exit(1);
2195
2644
  }
2196
2645
  }
@@ -2216,29 +2665,14 @@ function getErrorHint2(error) {
2216
2665
  // src/commands/list.ts
2217
2666
  init_esm_shims();
2218
2667
  init_api_service();
2219
- init_auth_service();
2220
- init_logger();
2221
- import boxen5 from "boxen";
2222
- import chalk8 from "chalk";
2668
+ import boxen7 from "boxen";
2223
2669
  import Table2 from "cli-table3";
2670
+ init_logger();
2224
2671
  async function listCommand(options = {}) {
2225
2672
  logger.log("");
2226
2673
  try {
2227
- const isAuthenticated = await authService.isAuthenticated();
2674
+ const isAuthenticated = await ensureAuthenticated();
2228
2675
  if (!isAuthenticated) {
2229
- logger.log(
2230
- boxen5(
2231
- `${chalk8.yellow.bold("\u26A0 Not authenticated")}
2232
-
2233
- Please run ${chalk8.cyan("bragduck init")} to login first.`,
2234
- {
2235
- padding: 1,
2236
- margin: 1,
2237
- borderStyle: "round",
2238
- borderColor: "yellow"
2239
- }
2240
- )
2241
- );
2242
2676
  process.exit(1);
2243
2677
  }
2244
2678
  const limit = options.limit || 50;
@@ -2259,7 +2693,9 @@ Please run ${chalk8.cyan("bragduck init")} to login first.`,
2259
2693
  if (search || tags) {
2260
2694
  logger.info("Try adjusting your filters or run without filters to see all brags");
2261
2695
  } else {
2262
- logger.info(`Run ${chalk8.cyan("bragduck scan")} to create your first brag!`);
2696
+ logger.info(
2697
+ theme.secondary("Run ") + theme.command("bragduck scan") + theme.secondary(" to create your first brag!")
2698
+ );
2263
2699
  }
2264
2700
  return;
2265
2701
  }
@@ -2273,7 +2709,7 @@ Please run ${chalk8.cyan("bragduck init")} to login first.`,
2273
2709
  `Showing ${offset + 1}-${offset + response.brags.length} of ${response.total} total brags`
2274
2710
  );
2275
2711
  logger.info(
2276
- chalk8.dim(`Run `) + chalk8.cyan(`bragduck list --offset ${nextOffset}`) + chalk8.dim(` to see more`)
2712
+ theme.secondary("Run ") + theme.command(`bragduck list --offset ${nextOffset}`) + theme.secondary(" to see more")
2277
2713
  );
2278
2714
  logger.log("");
2279
2715
  } else if (offset > 0) {
@@ -2286,51 +2722,41 @@ Please run ${chalk8.cyan("bragduck init")} to login first.`,
2286
2722
  const filterInfo = [];
2287
2723
  if (search) filterInfo.push(`search: "${search}"`);
2288
2724
  if (tags) filterInfo.push(`tags: ${tags.join(", ")}`);
2289
- logger.info(chalk8.dim(`Filters applied: ${filterInfo.join(", ")}`));
2725
+ logger.info(theme.info(`Filters applied: ${filterInfo.join(", ")}`));
2290
2726
  logger.log("");
2291
2727
  }
2292
2728
  } catch (error) {
2293
2729
  const err = error;
2294
2730
  logger.log("");
2295
- logger.log(
2296
- boxen5(formatErrorMessage(err.message, getErrorHint3(err)), {
2297
- padding: 1,
2298
- margin: 1,
2299
- borderStyle: "round",
2300
- borderColor: "red"
2301
- })
2302
- );
2731
+ logger.log(boxen7(formatErrorMessage(err.message, getErrorHint3(err)), boxStyles.error));
2303
2732
  process.exit(1);
2304
2733
  }
2305
2734
  }
2306
2735
  function formatBragsTable(brags) {
2307
2736
  const table = new Table2({
2308
2737
  head: [
2309
- chalk8.cyan("Date"),
2310
- chalk8.cyan("Title"),
2311
- chalk8.cyan("Description"),
2312
- chalk8.cyan("Tags"),
2313
- chalk8.cyan("Repository")
2738
+ colors.primary("Date"),
2739
+ colors.primary("Title"),
2740
+ colors.primary("Description"),
2741
+ colors.primary("Tags"),
2742
+ colors.primary("Repository")
2314
2743
  ],
2315
2744
  colWidths: [12, 30, 40, 20, 30],
2316
2745
  wordWrap: true,
2317
- style: {
2318
- head: [],
2319
- border: ["gray"]
2320
- }
2746
+ style: tableStyles.default.style
2321
2747
  });
2322
2748
  brags.forEach((brag) => {
2323
2749
  const date = new Date(brag.date).toLocaleDateString();
2324
2750
  const title = brag.title;
2325
2751
  const description = truncateText(brag.description, 100);
2326
- const tags = brag.tags.length > 0 ? brag.tags.join(", ") : chalk8.dim("none");
2327
- const repository = brag.repository ? truncateText(extractRepoName(brag.repository), 25) : chalk8.dim("none");
2752
+ const tags = brag.tags.length > 0 ? brag.tags.join(", ") : colors.dim("none");
2753
+ const repository = brag.repository ? truncateText(extractRepoName(brag.repository), 25) : colors.dim("none");
2328
2754
  table.push([
2329
- chalk8.yellow(date),
2330
- chalk8.white(title),
2331
- chalk8.dim(description),
2332
- chalk8.blue(tags),
2333
- chalk8.gray(repository)
2755
+ colors.highlight(date),
2756
+ colors.white(title),
2757
+ colors.dim(description),
2758
+ colors.primary(tags),
2759
+ colors.info(repository)
2334
2760
  ]);
2335
2761
  });
2336
2762
  return table.toString();
@@ -2367,8 +2793,8 @@ init_esm_shims();
2367
2793
  init_storage_service();
2368
2794
  init_logger();
2369
2795
  init_constants();
2370
- import boxen6 from "boxen";
2371
- import chalk9 from "chalk";
2796
+ import boxen8 from "boxen";
2797
+ import chalk6 from "chalk";
2372
2798
  import Table3 from "cli-table3";
2373
2799
  init_errors();
2374
2800
  var VALID_CONFIG_KEYS = Object.values(CONFIG_KEYS);
@@ -2400,9 +2826,10 @@ async function configCommand(subcommand, key, value) {
2400
2826
  );
2401
2827
  }
2402
2828
  } catch (error) {
2829
+ const err = error;
2403
2830
  logger.log("");
2404
2831
  logger.log(
2405
- boxen6(formatErrorMessage(error.message, getConfigHint(error)), {
2832
+ boxen8(formatErrorMessage(err.message, getConfigHint(err)), {
2406
2833
  padding: 1,
2407
2834
  margin: 1,
2408
2835
  borderStyle: "round",
@@ -2418,7 +2845,7 @@ async function handleListConfig() {
2418
2845
  autoVersionCheck: storageService.getConfig("autoVersionCheck")
2419
2846
  };
2420
2847
  const table = new Table3({
2421
- head: [chalk9.cyan("Key"), chalk9.cyan("Value"), chalk9.cyan("Default")],
2848
+ head: [chalk6.cyan("Key"), chalk6.cyan("Value"), chalk6.cyan("Default")],
2422
2849
  colWidths: [25, 40, 40],
2423
2850
  wordWrap: true,
2424
2851
  style: {
@@ -2427,36 +2854,36 @@ async function handleListConfig() {
2427
2854
  }
2428
2855
  });
2429
2856
  table.push([
2430
- chalk9.white("defaultCommitDays"),
2431
- chalk9.yellow(String(config2.defaultCommitDays)),
2432
- chalk9.dim(String(DEFAULT_CONFIG.defaultCommitDays))
2857
+ chalk6.white("defaultCommitDays"),
2858
+ chalk6.yellow(String(config2.defaultCommitDays)),
2859
+ chalk6.dim(String(DEFAULT_CONFIG.defaultCommitDays))
2433
2860
  ]);
2434
2861
  table.push([
2435
- chalk9.white("autoVersionCheck"),
2436
- chalk9.yellow(String(config2.autoVersionCheck)),
2437
- chalk9.dim(String(DEFAULT_CONFIG.autoVersionCheck))
2862
+ chalk6.white("autoVersionCheck"),
2863
+ chalk6.yellow(String(config2.autoVersionCheck)),
2864
+ chalk6.dim(String(DEFAULT_CONFIG.autoVersionCheck))
2438
2865
  ]);
2439
2866
  logger.info("Current configuration:");
2440
2867
  logger.log("");
2441
2868
  logger.log(table.toString());
2442
2869
  logger.log("");
2443
- logger.info(chalk9.dim("To change a value: ") + chalk9.cyan("bragduck config set <key> <value>"));
2870
+ logger.info(chalk6.dim("To change a value: ") + chalk6.cyan("bragduck config set <key> <value>"));
2444
2871
  logger.log("");
2445
2872
  }
2446
2873
  async function handleGetConfig(key) {
2447
2874
  validateConfigKey(key);
2448
2875
  const value = storageService.getConfig(key);
2449
2876
  const defaultValue = DEFAULT_CONFIG[key];
2450
- logger.info(`Configuration for ${chalk9.cyan(key)}:`);
2877
+ logger.info(`Configuration for ${chalk6.cyan(key)}:`);
2451
2878
  logger.log("");
2452
- logger.log(` ${chalk9.white("Current:")} ${chalk9.yellow(String(value))}`);
2453
- logger.log(` ${chalk9.white("Default:")} ${chalk9.dim(String(defaultValue))}`);
2879
+ logger.log(` ${chalk6.white("Current:")} ${chalk6.yellow(String(value))}`);
2880
+ logger.log(` ${chalk6.white("Default:")} ${chalk6.dim(String(defaultValue))}`);
2454
2881
  logger.log("");
2455
2882
  if (value === defaultValue) {
2456
- logger.info(chalk9.dim("Using default value"));
2883
+ logger.info(chalk6.dim("Using default value"));
2457
2884
  } else {
2458
2885
  logger.info(
2459
- chalk9.dim("Custom value set. Reset with: ") + chalk9.cyan(`bragduck config set ${key} ${defaultValue}`)
2886
+ chalk6.dim("Custom value set. Reset with: ") + chalk6.cyan(`bragduck config set ${key} ${defaultValue}`)
2460
2887
  );
2461
2888
  }
2462
2889
  logger.log("");
@@ -2466,10 +2893,10 @@ async function handleSetConfig(key, value) {
2466
2893
  const typedValue = validateAndConvertValue(key, value);
2467
2894
  storageService.setConfig(key, typedValue);
2468
2895
  logger.log(
2469
- boxen6(
2470
- `${chalk9.green.bold("\u2713 Configuration updated")}
2896
+ boxen8(
2897
+ `${chalk6.green.bold("\u2713 Configuration updated")}
2471
2898
 
2472
- ${chalk9.white(key)}: ${chalk9.yellow(String(typedValue))}`,
2899
+ ${chalk6.white(key)}: ${chalk6.yellow(String(typedValue))}`,
2473
2900
  {
2474
2901
  padding: 1,
2475
2902
  margin: 1,
@@ -2491,7 +2918,7 @@ ${VALID_CONFIG_KEYS.map((k) => ` - ${k}`).join("\n")}`
2491
2918
  }
2492
2919
  function validateAndConvertValue(key, value) {
2493
2920
  switch (key) {
2494
- case CONFIG_KEYS.DEFAULT_COMMIT_DAYS:
2921
+ case CONFIG_KEYS.DEFAULT_COMMIT_DAYS: {
2495
2922
  const days = parseInt(value, 10);
2496
2923
  if (isNaN(days) || days < 1 || days > 365) {
2497
2924
  throw new ValidationError(
@@ -2501,7 +2928,8 @@ Must be a number between 1 and 365`
2501
2928
  );
2502
2929
  }
2503
2930
  return days;
2504
- case CONFIG_KEYS.AUTO_VERSION_CHECK:
2931
+ }
2932
+ case CONFIG_KEYS.AUTO_VERSION_CHECK: {
2505
2933
  const lowerValue = value.toLowerCase();
2506
2934
  if (lowerValue === "true" || lowerValue === "1" || lowerValue === "yes") {
2507
2935
  return true;
@@ -2513,6 +2941,7 @@ Must be a number between 1 and 365`
2513
2941
 
2514
2942
  Must be one of: true, false, yes, no, 1, 0`
2515
2943
  );
2944
+ }
2516
2945
  default:
2517
2946
  throw new ValidationError(`Unknown config key: "${key}"`);
2518
2947
  }
@@ -2573,7 +3002,7 @@ program.command("config [subcommand] [key] [value]").description("Manage CLI con
2573
3002
  process.exit(1);
2574
3003
  }
2575
3004
  });
2576
- program.hook("preAction", async (_thisCommand) => {
3005
+ program.hook("preAction", async () => {
2577
3006
  const options = program.opts();
2578
3007
  if (options.debug) {
2579
3008
  process.env.DEBUG = "true";
@@ -2583,7 +3012,7 @@ program.hook("preAction", async (_thisCommand) => {
2583
3012
  if (!options.skipVersionCheck && !isVersionOrHelp) {
2584
3013
  try {
2585
3014
  await checkForUpdates({ silent: false });
2586
- } catch (error) {
3015
+ } catch {
2587
3016
  logger.debug("Version check failed, continuing...");
2588
3017
  }
2589
3018
  }