@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.
- package/dist/bin/bragduck.js +98 -50
- package/dist/bin/bragduck.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/bragduck.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
4997
|
-
|
|
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 (
|
|
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:
|
|
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(
|
|
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:
|
|
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,
|
|
5119
|
+
logger.log(formatRefinedCommitsTable(refinedBrags, selectedCommits));
|
|
5085
5120
|
logger.log("");
|
|
5086
|
-
acceptedBrags = await promptReviewBrags(refinedBrags,
|
|
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 =
|
|
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
|
-
|
|
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({
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
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 =
|
|
5951
|
+
const date = formatDate(brag.date);
|
|
5904
5952
|
return `${colors.highlight(date)} ${colors.white(brag.title)}`;
|
|
5905
5953
|
}).join("\n");
|
|
5906
5954
|
}
|