@bragduck/cli 2.18.1 → 2.22.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.
@@ -4485,6 +4485,22 @@ import boxen4 from "boxen";
4485
4485
  init_esm_shims();
4486
4486
  import Table from "cli-table3";
4487
4487
  import terminalLink from "terminal-link";
4488
+
4489
+ // src/utils/date-formatter.ts
4490
+ init_esm_shims();
4491
+ var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
4492
+ function formatDate(dateString) {
4493
+ const date = typeof dateString === "string" ? new Date(dateString) : dateString;
4494
+ if (isNaN(date.getTime())) {
4495
+ return "Invalid date";
4496
+ }
4497
+ const day = date.getDate().toString().padStart(2, "0");
4498
+ const month = MONTHS[date.getMonth()];
4499
+ const year = date.getFullYear();
4500
+ return `${day} ${month} ${year}`;
4501
+ }
4502
+
4503
+ // src/ui/formatters.ts
4488
4504
  function formatCommitChoice(commit) {
4489
4505
  let displaySha;
4490
4506
  if (commit.sha.startsWith("pr-")) {
@@ -4505,7 +4521,7 @@ function formatCommitChoice(commit) {
4505
4521
  }
4506
4522
  }
4507
4523
  const author = colors.info(`by ${commit.author}`);
4508
- const date = colors.info(new Date(commit.date).toLocaleDateString());
4524
+ const date = colors.info(formatDate(commit.date));
4509
4525
  let stats = "";
4510
4526
  let sizeIndicator = "";
4511
4527
  if (commit.diffStats) {
@@ -4579,19 +4595,29 @@ function formatSelectionSummary(count, commits) {
4579
4595
  return `
4580
4596
  ${selectedText}: ${stats}`;
4581
4597
  }
4582
- function formatSuccessMessage(count) {
4598
+ function formatSuccessMessage(count, brags) {
4583
4599
  const emoji = "\u{1F389}";
4584
4600
  const title = colors.successBold(
4585
4601
  `${emoji} Successfully created ${count} brag${count > 1 ? "s" : ""}!`
4586
4602
  );
4587
4603
  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");
4604
+ let bragsList = "";
4605
+ if (brags && brags.length > 0) {
4606
+ bragsList = "\n\n" + theme.secondary("Created brags:");
4607
+ for (const brag of brags) {
4608
+ const sourceLabel = brag.source.charAt(0).toUpperCase() + brag.source.slice(1).toLowerCase();
4609
+ const dateFormatted = formatDate(brag.date);
4610
+ bragsList += `
4611
+ ${colors.dim("\u2022")} ${colors.white(brag.title)} ${theme.secondary(`(${dateFormatted} \xB7 ${sourceLabel})`)}`;
4612
+ }
4613
+ }
4614
+ const hint = theme.secondary("\n\nRun ") + theme.command("bragduck list") + theme.secondary(" to see all your brags");
4589
4615
  const url = "https://bragduck.com/app/brags";
4590
4616
  const clickableUrl = terminalLink(url, url, {
4591
4617
  fallback: () => colors.primary(url)
4592
4618
  });
4593
4619
  const webUrl = theme.secondary("\n\nOr, check ") + clickableUrl + theme.secondary(" to see all your brags");
4594
- return `${title}${message}${hint}${webUrl}`;
4620
+ return `${title}${message}${bragsList}${hint}${webUrl}`;
4595
4621
  }
4596
4622
  function formatErrorMessage(message, hint) {
4597
4623
  const title = colors.errorBold("\u2717 Error");
@@ -4688,6 +4714,21 @@ async function promptSelectOrganisation(organisations) {
4688
4714
  });
4689
4715
  return selected === "none" ? null : selected;
4690
4716
  }
4717
+ async function promptSelectOrganisationWithDefault(organisations, defaultOrgId) {
4718
+ if (defaultOrgId) {
4719
+ const defaultOrg = organisations.find((org) => org.id === defaultOrgId);
4720
+ if (defaultOrg) {
4721
+ const useDefault = await confirm({
4722
+ message: `Use your default company "${defaultOrg.name}"?`,
4723
+ default: true
4724
+ });
4725
+ if (useDefault) {
4726
+ return defaultOrgId;
4727
+ }
4728
+ }
4729
+ }
4730
+ return await promptSelectOrganisation(organisations);
4731
+ }
4691
4732
  async function promptReviewBrags(refinedBrags, selectedCommits) {
4692
4733
  const acceptedBrags = [];
4693
4734
  console.log("\n" + theme.info("Review each brag before creation:") + "\n");
@@ -4913,7 +4954,7 @@ async function promptSelectService() {
4913
4954
  });
4914
4955
  return selected;
4915
4956
  }
4916
- async function syncSingleService(sourceType, options, TOTAL_STEPS) {
4957
+ async function syncSingleService(sourceType, options, TOTAL_STEPS, sharedDays, sharedOrgId) {
4917
4958
  const adapter = AdapterFactory.getAdapter(sourceType);
4918
4959
  const repoSpinner = createStepSpinner(2, TOTAL_STEPS, "Validating repository");
4919
4960
  repoSpinner.start();
@@ -4943,7 +4984,7 @@ async function syncSingleService(sourceType, options, TOTAL_STEPS) {
4943
4984
  failStepSpinner(repoSpinner, 2, TOTAL_STEPS, "Validation failed");
4944
4985
  throw error;
4945
4986
  }
4946
- let days = options.days;
4987
+ let days = sharedDays || options.days;
4947
4988
  if (!days && options.today) {
4948
4989
  days = 1;
4949
4990
  logger.debug("Using --today flag: scanning last 24 hours (1 day)");
@@ -4978,8 +5019,30 @@ async function syncSingleService(sourceType, options, TOTAL_STEPS) {
4978
5019
  logger.log("");
4979
5020
  logger.log(formatCommitStats(workItems));
4980
5021
  logger.log("");
4981
- let sortedCommits = [...workItems];
4982
- if (workItems.length > 1 && !options.turbo) {
5022
+ const existingBrags = await apiService.listBrags({ limit: 100 });
5023
+ logger.debug(`Fetched ${existingBrags.brags.length} existing brags`);
5024
+ const existingUrls = new Set(existingBrags.brags.flatMap((b) => b.attachments || []));
5025
+ logger.debug(`Existing URLs in attachments: ${existingUrls.size}`);
5026
+ const duplicates = workItems.filter((c) => c.url && existingUrls.has(c.url));
5027
+ const newWorkItems = workItems.filter((c) => !c.url || !existingUrls.has(c.url));
5028
+ logger.debug(`Found ${duplicates.length} duplicates, ${newWorkItems.length} new items`);
5029
+ if (duplicates.length > 0) {
5030
+ logger.log("");
5031
+ logger.info(
5032
+ colors.dim(
5033
+ `\u2139 ${duplicates.length} work item${duplicates.length > 1 ? "s" : ""} already exist in Bragduck (will be skipped)`
5034
+ )
5035
+ );
5036
+ logger.log("");
5037
+ }
5038
+ if (newWorkItems.length === 0) {
5039
+ logger.log("");
5040
+ logger.info(theme.secondary("All work items already exist in Bragduck. Nothing to sync."));
5041
+ logger.log("");
5042
+ return { created: 0, skipped: duplicates.length };
5043
+ }
5044
+ let sortedCommits = [...newWorkItems];
5045
+ if (newWorkItems.length > 1 && !options.turbo) {
4983
5046
  const sortOption = await promptSortOption();
4984
5047
  logger.log("");
4985
5048
  if (sortOption === "date") {
@@ -4997,58 +5060,34 @@ async function syncSingleService(sourceType, options, TOTAL_STEPS) {
4997
5060
  return filesB - filesA;
4998
5061
  });
4999
5062
  }
5000
- } else if (workItems.length > 1 && options.turbo) {
5063
+ } else if (newWorkItems.length > 1 && options.turbo) {
5001
5064
  sortedCommits.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
5002
5065
  logger.debug("Turbo mode: sorted by date (most recent first)");
5003
5066
  }
5004
5067
  let selectedShas;
5005
5068
  if (options.turbo) {
5006
5069
  selectedShas = sortedCommits.map((c) => c.sha);
5007
- logger.debug(`Turbo mode: auto-selected all ${selectedShas.length} items`);
5070
+ logger.debug(`Turbo mode: auto-selected all ${selectedShas.length} new items`);
5008
5071
  } else {
5009
5072
  selectedShas = await promptSelectCommits(sortedCommits);
5010
5073
  if (selectedShas.length === 0) {
5011
5074
  logger.log("");
5012
5075
  logger.info(theme.secondary("No work items selected. Sync cancelled."));
5013
5076
  logger.log("");
5014
- return { created: 0, skipped: 0 };
5077
+ return { created: 0, skipped: duplicates.length };
5015
5078
  }
5016
5079
  }
5017
5080
  const selectedCommits = sortedCommits.filter((c) => selectedShas.includes(c.sha));
5018
5081
  logger.log(formatSelectionSummary(selectedCommits.length, selectedCommits));
5019
5082
  logger.log("");
5020
- const existingBrags = await apiService.listBrags({ limit: 100 });
5021
- logger.debug(`Fetched ${existingBrags.brags.length} existing brags`);
5022
- const existingUrls = new Set(existingBrags.brags.flatMap((b) => b.attachments || []));
5023
- logger.debug(`Existing URLs in attachments: ${existingUrls.size}`);
5024
- const duplicates = selectedCommits.filter((c) => c.url && existingUrls.has(c.url));
5025
- const newCommits = selectedCommits.filter((c) => !c.url || !existingUrls.has(c.url));
5026
- logger.debug(`Duplicates: ${duplicates.length}, New: ${newCommits.length}`);
5027
- if (duplicates.length > 0) {
5028
- logger.log("");
5029
- logger.info(
5030
- colors.warning(
5031
- `${duplicates.length} work item${duplicates.length > 1 ? "s" : ""} already added to Bragduck - skipping`
5032
- )
5033
- );
5034
- logger.log("");
5035
- }
5036
- if (newCommits.length === 0) {
5037
- logger.log("");
5038
- logger.info(
5039
- theme.secondary("All selected work items already exist in Bragduck. Nothing to refine.")
5040
- );
5041
- logger.log("");
5042
- return { created: 0, skipped: duplicates.length };
5043
- }
5044
5083
  const refineSpinner = createStepSpinner(
5045
5084
  4,
5046
5085
  TOTAL_STEPS,
5047
- `Refining ${theme.count(newCommits.length)} work item${newCommits.length > 1 ? "s" : ""} with AI`
5086
+ `Refining ${theme.count(selectedCommits.length)} work item${selectedCommits.length > 1 ? "s" : ""} with AI`
5048
5087
  );
5049
5088
  refineSpinner.start();
5050
5089
  const refineRequest = {
5051
- brags: newCommits.map((c) => ({
5090
+ brags: selectedCommits.map((c) => ({
5052
5091
  text: c.message,
5053
5092
  date: c.date,
5054
5093
  title: c.message.split("\n")[0]
@@ -5066,9 +5105,9 @@ async function syncSingleService(sourceType, options, TOTAL_STEPS) {
5066
5105
  } else {
5067
5106
  logger.info("Preview of refined brags:");
5068
5107
  logger.log("");
5069
- logger.log(formatRefinedCommitsTable(refinedBrags, newCommits));
5108
+ logger.log(formatRefinedCommitsTable(refinedBrags, selectedCommits));
5070
5109
  logger.log("");
5071
- acceptedBrags = await promptReviewBrags(refinedBrags, newCommits);
5110
+ acceptedBrags = await promptReviewBrags(refinedBrags, selectedCommits);
5072
5111
  }
5073
5112
  if (acceptedBrags.length === 0) {
5074
5113
  logger.log("");
@@ -5077,17 +5116,19 @@ async function syncSingleService(sourceType, options, TOTAL_STEPS) {
5077
5116
  return { created: 0, skipped: duplicates.length };
5078
5117
  }
5079
5118
  logger.log("");
5080
- let selectedOrgId = null;
5081
- const userInfo = authService.getUserInfo();
5082
- if (userInfo?.id) {
5083
- try {
5084
- const orgsResponse = await apiService.listUserOrganisations(userInfo.id);
5085
- if (orgsResponse.items.length > 0) {
5086
- selectedOrgId = await promptSelectOrganisation(orgsResponse.items);
5087
- logger.log("");
5119
+ let selectedOrgId = sharedOrgId !== void 0 ? sharedOrgId : null;
5120
+ if (sharedOrgId === void 0) {
5121
+ const userInfo = authService.getUserInfo();
5122
+ if (userInfo?.id) {
5123
+ try {
5124
+ const orgsResponse = await apiService.listUserOrganisations(userInfo.id);
5125
+ if (orgsResponse.items.length > 0) {
5126
+ selectedOrgId = await promptSelectOrganisation(orgsResponse.items);
5127
+ logger.log("");
5128
+ }
5129
+ } catch {
5130
+ logger.debug("Failed to fetch organisations, skipping org selection");
5088
5131
  }
5089
- } catch {
5090
- logger.debug("Failed to fetch organisations, skipping org selection");
5091
5132
  }
5092
5133
  }
5093
5134
  const createSpinner2 = createStepSpinner(
@@ -5098,7 +5139,7 @@ async function syncSingleService(sourceType, options, TOTAL_STEPS) {
5098
5139
  createSpinner2.start();
5099
5140
  const createRequest = {
5100
5141
  brags: acceptedBrags.map((refined, index) => {
5101
- const originalCommit = newCommits[index];
5142
+ const originalCommit = selectedCommits[index];
5102
5143
  return {
5103
5144
  commit_sha: originalCommit?.sha || `brag-${index}`,
5104
5145
  title: refined.refined_title,
@@ -5145,7 +5186,12 @@ async function syncSingleService(sourceType, options, TOTAL_STEPS) {
5145
5186
  logger.log("");
5146
5187
  throw error;
5147
5188
  }
5148
- return { created: createResponse.created, skipped: duplicates.length };
5189
+ const createdBrags = createResponse.brags.map((brag) => ({
5190
+ title: brag.title,
5191
+ date: brag.created_at,
5192
+ source: sourceType
5193
+ }));
5194
+ return { created: createResponse.created, skipped: duplicates.length, createdBrags };
5149
5195
  }
5150
5196
  async function syncAllAuthenticatedServices(options) {
5151
5197
  const TOTAL_STEPS = 5;
@@ -5161,16 +5207,28 @@ async function syncAllAuthenticatedServices(options) {
5161
5207
  let servicesToSync = authenticatedServices.filter(
5162
5208
  (service) => service !== "bragduck" && allServices.includes(service)
5163
5209
  );
5210
+ let detectedGitService = null;
5164
5211
  try {
5165
5212
  const { detected } = await sourceDetector.detectSources();
5166
5213
  logger.debug(`Detected ${detected.length} git source(s) from repository`);
5167
- for (const source of detected) {
5168
- if (["github", "gitlab", "bitbucket", "atlassian"].includes(source.type)) {
5169
- if (!servicesToSync.includes(source.type)) {
5170
- servicesToSync.push(source.type);
5171
- logger.debug(`Added local ${source.type} repo to sync list`);
5172
- }
5173
- }
5214
+ const gitSource = detected.find(
5215
+ (source) => ["github", "gitlab", "bitbucket", "atlassian"].includes(source.type)
5216
+ );
5217
+ if (gitSource) {
5218
+ detectedGitService = gitSource.type;
5219
+ logger.debug(`Current repository is ${detectedGitService}, will sync only this git service`);
5220
+ if (!servicesToSync.includes(detectedGitService)) {
5221
+ servicesToSync.push(detectedGitService);
5222
+ logger.debug(`Added local ${detectedGitService} repo to sync list`);
5223
+ }
5224
+ const otherGitServices = ["github", "gitlab", "bitbucket", "atlassian"];
5225
+ servicesToSync = servicesToSync.filter(
5226
+ (service) => service === detectedGitService || !otherGitServices.includes(service)
5227
+ // Keep non-git services like Jira/Confluence
5228
+ );
5229
+ logger.debug(
5230
+ `Filtered out other git services, keeping only ${detectedGitService} for git sync`
5231
+ );
5174
5232
  }
5175
5233
  } catch {
5176
5234
  logger.debug("No local git repository detected");
@@ -5188,10 +5246,40 @@ async function syncAllAuthenticatedServices(options) {
5188
5246
  }
5189
5247
  logger.info(
5190
5248
  theme.highlight(
5191
- `Syncing ${servicesToSync.length} authenticated service${servicesToSync.length > 1 ? "s" : ""}...`
5249
+ `Syncing ${servicesToSync.length} service${servicesToSync.length > 1 ? "s" : ""}...`
5192
5250
  )
5193
5251
  );
5194
5252
  logger.log("");
5253
+ let sharedDays = options.days;
5254
+ if (!sharedDays && options.today) {
5255
+ sharedDays = 1;
5256
+ logger.debug("Using --today flag: scanning last 24 hours (1 day)");
5257
+ }
5258
+ if (!sharedDays && !options.turbo) {
5259
+ const defaultDays = storageService.getConfig("defaultCommitDays");
5260
+ sharedDays = await promptDaysToScan(defaultDays);
5261
+ logger.log("");
5262
+ }
5263
+ if (!sharedDays && options.turbo) {
5264
+ sharedDays = storageService.getConfig("defaultCommitDays") || 30;
5265
+ logger.debug(`Turbo mode: using default ${sharedDays} days`);
5266
+ }
5267
+ let sharedOrgId = null;
5268
+ if (!options.turbo) {
5269
+ const userInfo = authService.getUserInfo();
5270
+ if (userInfo?.id) {
5271
+ try {
5272
+ const orgsResponse = await apiService.listUserOrganisations(userInfo.id);
5273
+ if (orgsResponse.items.length > 0) {
5274
+ const defaultOrgId = storageService.getConfig("defaultCompany");
5275
+ sharedOrgId = await promptSelectOrganisationWithDefault(orgsResponse.items, defaultOrgId);
5276
+ logger.log("");
5277
+ }
5278
+ } catch {
5279
+ logger.debug("Failed to fetch organisations, skipping org selection");
5280
+ }
5281
+ }
5282
+ }
5195
5283
  const results = [];
5196
5284
  for (let i = 0; i < servicesToSync.length; i++) {
5197
5285
  const service = servicesToSync[i];
@@ -5202,8 +5290,19 @@ async function syncAllAuthenticatedServices(options) {
5202
5290
  );
5203
5291
  logger.log("");
5204
5292
  try {
5205
- const result = await syncSingleService(service, options, TOTAL_STEPS);
5206
- results.push({ service, created: result.created, skipped: result.skipped });
5293
+ const result = await syncSingleService(
5294
+ service,
5295
+ options,
5296
+ TOTAL_STEPS,
5297
+ sharedDays,
5298
+ sharedOrgId
5299
+ );
5300
+ results.push({
5301
+ service,
5302
+ created: result.created,
5303
+ skipped: result.skipped,
5304
+ createdBrags: result.createdBrags
5305
+ });
5207
5306
  logger.log("");
5208
5307
  } catch (error) {
5209
5308
  const err = error;
@@ -5244,7 +5343,8 @@ async function syncAllAuthenticatedServices(options) {
5244
5343
  logger.log("");
5245
5344
  }
5246
5345
  if (totalCreated > 0) {
5247
- logger.log(boxen6(formatSuccessMessage(totalCreated), boxStyles.success));
5346
+ const allCreatedBrags = results.filter((r) => r.createdBrags).flatMap((r) => r.createdBrags);
5347
+ logger.log(boxen6(formatSuccessMessage(totalCreated, allCreatedBrags), boxStyles.success));
5248
5348
  } else if (successful.length > 0) {
5249
5349
  logger.log(
5250
5350
  boxen6(
@@ -5332,7 +5432,9 @@ async function syncCommand(options = {}) {
5332
5432
  logger.log("");
5333
5433
  const result = await syncSingleService(sourceType, options, TOTAL_STEPS);
5334
5434
  if (result.created > 0) {
5335
- logger.log(boxen6(formatSuccessMessage(result.created), boxStyles.success));
5435
+ logger.log(
5436
+ boxen6(formatSuccessMessage(result.created, result.createdBrags), boxStyles.success)
5437
+ );
5336
5438
  } else if (result.skipped > 0) {
5337
5439
  logger.log("");
5338
5440
  logger.info(
@@ -5818,7 +5920,7 @@ function formatBragsTable(brags) {
5818
5920
  style: tableStyles.default.style
5819
5921
  });
5820
5922
  brags.forEach((brag) => {
5821
- const date = new Date(brag.date).toLocaleDateString();
5923
+ const date = formatDate(brag.date);
5822
5924
  const title = brag.title;
5823
5925
  const description = truncateText(brag.description, 100);
5824
5926
  const tags = brag.tags.length > 0 ? brag.tags.join(", ") : colors.dim("none");
@@ -5835,7 +5937,7 @@ function formatBragsTable(brags) {
5835
5937
  }
5836
5938
  function formatBragsOneline(brags) {
5837
5939
  return brags.map((brag) => {
5838
- const date = new Date(brag.date).toLocaleDateString();
5940
+ const date = formatDate(brag.date);
5839
5941
  return `${colors.highlight(date)} ${colors.white(brag.title)}`;
5840
5942
  }).join("\n");
5841
5943
  }