@bragduck/cli 2.20.0 → 2.23.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.
@@ -1907,12 +1907,23 @@ var execAsync2 = promisify2(exec2);
1907
1907
  var GitHubService = class {
1908
1908
  MAX_BODY_LENGTH = 5e3;
1909
1909
  PR_SEARCH_FIELDS = "number,title,body,author,mergedAt,additions,deletions,changedFiles,url,labels";
1910
+ /**
1911
+ * Execute a gh CLI command with clean environment
1912
+ * Unsets GITHUB_TOKEN and GH_TOKEN to prevent .envrc tokens from interfering
1913
+ * with gh CLI's own authentication
1914
+ */
1915
+ async execGhCommand(command) {
1916
+ const env = { ...process.env };
1917
+ delete env.GITHUB_TOKEN;
1918
+ delete env.GH_TOKEN;
1919
+ return execAsync2(command, { env });
1920
+ }
1910
1921
  /**
1911
1922
  * Check if GitHub CLI is installed and available
1912
1923
  */
1913
1924
  async checkGitHubCLI() {
1914
1925
  try {
1915
- await execAsync2("command gh --version");
1926
+ await this.execGhCommand("command gh --version");
1916
1927
  return true;
1917
1928
  } catch {
1918
1929
  return false;
@@ -1934,7 +1945,7 @@ var GitHubService = class {
1934
1945
  */
1935
1946
  async checkAuthentication() {
1936
1947
  try {
1937
- await execAsync2("command gh auth status");
1948
+ await this.execGhCommand("command gh auth status");
1938
1949
  return true;
1939
1950
  } catch {
1940
1951
  return false;
@@ -1971,7 +1982,7 @@ var GitHubService = class {
1971
1982
  await this.ensureGitHubCLI();
1972
1983
  await this.ensureAuthentication();
1973
1984
  await gitService.validateRepository();
1974
- const { stdout } = await execAsync2("command gh repo view --json url");
1985
+ const { stdout } = await this.execGhCommand("command gh repo view --json url");
1975
1986
  const data = JSON.parse(stdout);
1976
1987
  if (!data.url) {
1977
1988
  throw new GitHubError("This repository is not hosted on GitHub", {
@@ -2003,7 +2014,7 @@ var GitHubService = class {
2003
2014
  async getRepositoryInfo() {
2004
2015
  try {
2005
2016
  await this.ensureGitHubCLI();
2006
- const { stdout } = await execAsync2(
2017
+ const { stdout } = await this.execGhCommand(
2007
2018
  "command gh repo view --json owner,name,url,nameWithOwner"
2008
2019
  );
2009
2020
  const data = JSON.parse(stdout);
@@ -2028,7 +2039,7 @@ var GitHubService = class {
2028
2039
  */
2029
2040
  async getCurrentGitHubUser() {
2030
2041
  try {
2031
- const { stdout } = await execAsync2("command gh api user --jq .login");
2042
+ const { stdout } = await this.execGhCommand("command gh api user --jq .login");
2032
2043
  return stdout.trim() || null;
2033
2044
  } catch {
2034
2045
  logger.debug("Failed to get GitHub user");
@@ -2054,7 +2065,7 @@ var GitHubService = class {
2054
2065
  const limitArg = limit ? `--limit ${limit}` : "";
2055
2066
  const command = `command gh pr list --state merged --json ${this.PR_SEARCH_FIELDS} --search "${searchQuery}" ${limitArg}`;
2056
2067
  logger.debug(`Running: ${command}`);
2057
- const { stdout } = await execAsync2(command);
2068
+ const { stdout } = await this.execGhCommand(command);
2058
2069
  const prs = JSON.parse(stdout);
2059
2070
  logger.debug(`Found ${prs.length} merged PRs`);
2060
2071
  return prs;
@@ -4485,6 +4496,22 @@ import boxen4 from "boxen";
4485
4496
  init_esm_shims();
4486
4497
  import Table from "cli-table3";
4487
4498
  import terminalLink from "terminal-link";
4499
+
4500
+ // src/utils/date-formatter.ts
4501
+ init_esm_shims();
4502
+ var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
4503
+ function formatDate(dateString) {
4504
+ const date = typeof dateString === "string" ? new Date(dateString) : dateString;
4505
+ if (isNaN(date.getTime())) {
4506
+ return "Invalid date";
4507
+ }
4508
+ const day = date.getDate().toString().padStart(2, "0");
4509
+ const month = MONTHS[date.getMonth()];
4510
+ const year = date.getFullYear();
4511
+ return `${day} ${month} ${year}`;
4512
+ }
4513
+
4514
+ // src/ui/formatters.ts
4488
4515
  function formatCommitChoice(commit) {
4489
4516
  let displaySha;
4490
4517
  if (commit.sha.startsWith("pr-")) {
@@ -4505,7 +4532,7 @@ function formatCommitChoice(commit) {
4505
4532
  }
4506
4533
  }
4507
4534
  const author = colors.info(`by ${commit.author}`);
4508
- const date = colors.info(new Date(commit.date).toLocaleDateString());
4535
+ const date = colors.info(formatDate(commit.date));
4509
4536
  let stats = "";
4510
4537
  let sizeIndicator = "";
4511
4538
  if (commit.diffStats) {
@@ -4579,19 +4606,29 @@ function formatSelectionSummary(count, commits) {
4579
4606
  return `
4580
4607
  ${selectedText}: ${stats}`;
4581
4608
  }
4582
- function formatSuccessMessage(count) {
4609
+ function formatSuccessMessage(count, brags) {
4583
4610
  const emoji = "\u{1F389}";
4584
4611
  const title = colors.successBold(
4585
4612
  `${emoji} Successfully created ${count} brag${count > 1 ? "s" : ""}!`
4586
4613
  );
4587
4614
  const message = colors.white("\nYour achievements are now saved and ready to showcase.");
4588
- const hint = theme.secondary("\nRun ") + theme.command("bragduck list") + theme.secondary(" to see all your brags");
4615
+ let bragsList = "";
4616
+ if (brags && brags.length > 0) {
4617
+ bragsList = "\n\n" + theme.secondary("Created brags:");
4618
+ for (const brag of brags) {
4619
+ const sourceLabel = brag.source.charAt(0).toUpperCase() + brag.source.slice(1).toLowerCase();
4620
+ const dateFormatted = formatDate(brag.date);
4621
+ bragsList += `
4622
+ ${colors.dim("\u2022")} ${colors.white(brag.title)} ${theme.secondary(`(${dateFormatted} \xB7 ${sourceLabel})`)}`;
4623
+ }
4624
+ }
4625
+ const hint = theme.secondary("\n\nRun ") + theme.command("bragduck list") + theme.secondary(" to see all your brags");
4589
4626
  const url = "https://bragduck.com/app/brags";
4590
4627
  const clickableUrl = terminalLink(url, url, {
4591
4628
  fallback: () => colors.primary(url)
4592
4629
  });
4593
4630
  const webUrl = theme.secondary("\n\nOr, check ") + clickableUrl + theme.secondary(" to see all your brags");
4594
- return `${title}${message}${hint}${webUrl}`;
4631
+ return `${title}${message}${bragsList}${hint}${webUrl}`;
4595
4632
  }
4596
4633
  function formatErrorMessage(message, hint) {
4597
4634
  const title = colors.errorBold("\u2717 Error");
@@ -4993,8 +5030,30 @@ async function syncSingleService(sourceType, options, TOTAL_STEPS, sharedDays, s
4993
5030
  logger.log("");
4994
5031
  logger.log(formatCommitStats(workItems));
4995
5032
  logger.log("");
4996
- let sortedCommits = [...workItems];
4997
- if (workItems.length > 1 && !options.turbo) {
5033
+ const existingBrags = await apiService.listBrags({ limit: 100 });
5034
+ logger.debug(`Fetched ${existingBrags.brags.length} existing brags`);
5035
+ const existingUrls = new Set(existingBrags.brags.flatMap((b) => b.attachments || []));
5036
+ logger.debug(`Existing URLs in attachments: ${existingUrls.size}`);
5037
+ const duplicates = workItems.filter((c) => c.url && existingUrls.has(c.url));
5038
+ const newWorkItems = workItems.filter((c) => !c.url || !existingUrls.has(c.url));
5039
+ logger.debug(`Found ${duplicates.length} duplicates, ${newWorkItems.length} new items`);
5040
+ if (duplicates.length > 0) {
5041
+ logger.log("");
5042
+ logger.info(
5043
+ colors.dim(
5044
+ `\u2139 ${duplicates.length} work item${duplicates.length > 1 ? "s" : ""} already exist in Bragduck (will be skipped)`
5045
+ )
5046
+ );
5047
+ logger.log("");
5048
+ }
5049
+ if (newWorkItems.length === 0) {
5050
+ logger.log("");
5051
+ logger.info(theme.secondary("All work items already exist in Bragduck. Nothing to sync."));
5052
+ logger.log("");
5053
+ return { created: 0, skipped: duplicates.length };
5054
+ }
5055
+ let sortedCommits = [...newWorkItems];
5056
+ if (newWorkItems.length > 1 && !options.turbo) {
4998
5057
  const sortOption = await promptSortOption();
4999
5058
  logger.log("");
5000
5059
  if (sortOption === "date") {
@@ -5012,58 +5071,34 @@ async function syncSingleService(sourceType, options, TOTAL_STEPS, sharedDays, s
5012
5071
  return filesB - filesA;
5013
5072
  });
5014
5073
  }
5015
- } else if (workItems.length > 1 && options.turbo) {
5074
+ } else if (newWorkItems.length > 1 && options.turbo) {
5016
5075
  sortedCommits.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
5017
5076
  logger.debug("Turbo mode: sorted by date (most recent first)");
5018
5077
  }
5019
5078
  let selectedShas;
5020
5079
  if (options.turbo) {
5021
5080
  selectedShas = sortedCommits.map((c) => c.sha);
5022
- logger.debug(`Turbo mode: auto-selected all ${selectedShas.length} items`);
5081
+ logger.debug(`Turbo mode: auto-selected all ${selectedShas.length} new items`);
5023
5082
  } else {
5024
5083
  selectedShas = await promptSelectCommits(sortedCommits);
5025
5084
  if (selectedShas.length === 0) {
5026
5085
  logger.log("");
5027
5086
  logger.info(theme.secondary("No work items selected. Sync cancelled."));
5028
5087
  logger.log("");
5029
- return { created: 0, skipped: 0 };
5088
+ return { created: 0, skipped: duplicates.length };
5030
5089
  }
5031
5090
  }
5032
5091
  const selectedCommits = sortedCommits.filter((c) => selectedShas.includes(c.sha));
5033
5092
  logger.log(formatSelectionSummary(selectedCommits.length, selectedCommits));
5034
5093
  logger.log("");
5035
- const existingBrags = await apiService.listBrags({ limit: 100 });
5036
- logger.debug(`Fetched ${existingBrags.brags.length} existing brags`);
5037
- const existingUrls = new Set(existingBrags.brags.flatMap((b) => b.attachments || []));
5038
- logger.debug(`Existing URLs in attachments: ${existingUrls.size}`);
5039
- const duplicates = selectedCommits.filter((c) => c.url && existingUrls.has(c.url));
5040
- const newCommits = selectedCommits.filter((c) => !c.url || !existingUrls.has(c.url));
5041
- logger.debug(`Duplicates: ${duplicates.length}, New: ${newCommits.length}`);
5042
- if (duplicates.length > 0) {
5043
- logger.log("");
5044
- logger.info(
5045
- colors.warning(
5046
- `${duplicates.length} work item${duplicates.length > 1 ? "s" : ""} already added to Bragduck - skipping`
5047
- )
5048
- );
5049
- logger.log("");
5050
- }
5051
- if (newCommits.length === 0) {
5052
- logger.log("");
5053
- logger.info(
5054
- theme.secondary("All selected work items already exist in Bragduck. Nothing to refine.")
5055
- );
5056
- logger.log("");
5057
- return { created: 0, skipped: duplicates.length };
5058
- }
5059
5094
  const refineSpinner = createStepSpinner(
5060
5095
  4,
5061
5096
  TOTAL_STEPS,
5062
- `Refining ${theme.count(newCommits.length)} work item${newCommits.length > 1 ? "s" : ""} with AI`
5097
+ `Refining ${theme.count(selectedCommits.length)} work item${selectedCommits.length > 1 ? "s" : ""} with AI`
5063
5098
  );
5064
5099
  refineSpinner.start();
5065
5100
  const refineRequest = {
5066
- brags: newCommits.map((c) => ({
5101
+ brags: selectedCommits.map((c) => ({
5067
5102
  text: c.message,
5068
5103
  date: c.date,
5069
5104
  title: c.message.split("\n")[0]
@@ -5081,9 +5116,9 @@ async function syncSingleService(sourceType, options, TOTAL_STEPS, sharedDays, s
5081
5116
  } else {
5082
5117
  logger.info("Preview of refined brags:");
5083
5118
  logger.log("");
5084
- logger.log(formatRefinedCommitsTable(refinedBrags, newCommits));
5119
+ logger.log(formatRefinedCommitsTable(refinedBrags, selectedCommits));
5085
5120
  logger.log("");
5086
- acceptedBrags = await promptReviewBrags(refinedBrags, newCommits);
5121
+ acceptedBrags = await promptReviewBrags(refinedBrags, selectedCommits);
5087
5122
  }
5088
5123
  if (acceptedBrags.length === 0) {
5089
5124
  logger.log("");
@@ -5115,7 +5150,7 @@ async function syncSingleService(sourceType, options, TOTAL_STEPS, sharedDays, s
5115
5150
  createSpinner2.start();
5116
5151
  const createRequest = {
5117
5152
  brags: acceptedBrags.map((refined, index) => {
5118
- const originalCommit = newCommits[index];
5153
+ const originalCommit = selectedCommits[index];
5119
5154
  return {
5120
5155
  commit_sha: originalCommit?.sha || `brag-${index}`,
5121
5156
  title: refined.refined_title,
@@ -5162,7 +5197,12 @@ async function syncSingleService(sourceType, options, TOTAL_STEPS, sharedDays, s
5162
5197
  logger.log("");
5163
5198
  throw error;
5164
5199
  }
5165
- return { created: createResponse.created, skipped: duplicates.length };
5200
+ const createdBrags = createResponse.brags.map((brag) => ({
5201
+ title: brag.title,
5202
+ date: brag.created_at,
5203
+ source: sourceType
5204
+ }));
5205
+ return { created: createResponse.created, skipped: duplicates.length, createdBrags };
5166
5206
  }
5167
5207
  async function syncAllAuthenticatedServices(options) {
5168
5208
  const TOTAL_STEPS = 5;
@@ -5268,7 +5308,12 @@ async function syncAllAuthenticatedServices(options) {
5268
5308
  sharedDays,
5269
5309
  sharedOrgId
5270
5310
  );
5271
- results.push({ service, created: result.created, skipped: result.skipped });
5311
+ results.push({
5312
+ service,
5313
+ created: result.created,
5314
+ skipped: result.skipped,
5315
+ createdBrags: result.createdBrags
5316
+ });
5272
5317
  logger.log("");
5273
5318
  } catch (error) {
5274
5319
  const err = error;
@@ -5309,7 +5354,8 @@ async function syncAllAuthenticatedServices(options) {
5309
5354
  logger.log("");
5310
5355
  }
5311
5356
  if (totalCreated > 0) {
5312
- logger.log(boxen6(formatSuccessMessage(totalCreated), boxStyles.success));
5357
+ const allCreatedBrags = results.filter((r) => r.createdBrags).flatMap((r) => r.createdBrags);
5358
+ logger.log(boxen6(formatSuccessMessage(totalCreated, allCreatedBrags), boxStyles.success));
5313
5359
  } else if (successful.length > 0) {
5314
5360
  logger.log(
5315
5361
  boxen6(
@@ -5397,7 +5443,9 @@ async function syncCommand(options = {}) {
5397
5443
  logger.log("");
5398
5444
  const result = await syncSingleService(sourceType, options, TOTAL_STEPS);
5399
5445
  if (result.created > 0) {
5400
- logger.log(boxen6(formatSuccessMessage(result.created), boxStyles.success));
5446
+ logger.log(
5447
+ boxen6(formatSuccessMessage(result.created, result.createdBrags), boxStyles.success)
5448
+ );
5401
5449
  } else if (result.skipped > 0) {
5402
5450
  logger.log("");
5403
5451
  logger.info(
@@ -5883,7 +5931,7 @@ function formatBragsTable(brags) {
5883
5931
  style: tableStyles.default.style
5884
5932
  });
5885
5933
  brags.forEach((brag) => {
5886
- const date = new Date(brag.date).toLocaleDateString();
5934
+ const date = formatDate(brag.date);
5887
5935
  const title = brag.title;
5888
5936
  const description = truncateText(brag.description, 100);
5889
5937
  const tags = brag.tags.length > 0 ? brag.tags.join(", ") : colors.dim("none");
@@ -5900,7 +5948,7 @@ function formatBragsTable(brags) {
5900
5948
  }
5901
5949
  function formatBragsOneline(brags) {
5902
5950
  return brags.map((brag) => {
5903
- const date = new Date(brag.date).toLocaleDateString();
5951
+ const date = formatDate(brag.date);
5904
5952
  return `${colors.highlight(date)} ${colors.white(brag.title)}`;
5905
5953
  }).join("\n");
5906
5954
  }