@bragduck/cli 2.38.2 → 2.39.1
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 +824 -116
- package/dist/bin/bragduck.js.map +1 -1
- package/dist/index.js +123 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/bragduck.js
CHANGED
|
@@ -104,6 +104,14 @@ var init_constants = __esm({
|
|
|
104
104
|
ORGANISATIONS: {
|
|
105
105
|
LIST: "/v1/users/{userId}/organisations"
|
|
106
106
|
},
|
|
107
|
+
STORIES: {
|
|
108
|
+
LIST: "/v1/users/{userId}/stories",
|
|
109
|
+
CREATE: "/v1/users/{userId}/stories",
|
|
110
|
+
DELETE: "/v1/users/{userId}/stories/{storyId}"
|
|
111
|
+
},
|
|
112
|
+
INTEGRATIONS: {
|
|
113
|
+
ACCESS_TOKEN: "/v1/integrations/{provider}/access-token"
|
|
114
|
+
},
|
|
107
115
|
VERSION: "/v1/cli/version",
|
|
108
116
|
/**
|
|
109
117
|
* @deprecated Use BRAGS.REFINE instead
|
|
@@ -188,7 +196,7 @@ var init_env_loader = __esm({
|
|
|
188
196
|
});
|
|
189
197
|
|
|
190
198
|
// src/utils/errors.ts
|
|
191
|
-
var BragduckError, AuthenticationError, GitError, ApiError, NetworkError, ValidationError, OAuthError, TokenExpiredError, GitHubError, BitbucketError, GitLabError, JiraError, ConfluenceError, AtlassianError, GoogleDocsError, SlackError;
|
|
199
|
+
var BragduckError, AuthenticationError, GitError, ApiError, NetworkError, ValidationError, OAuthError, TokenExpiredError, GitHubError, BitbucketError, GitLabError, JiraError, ConfluenceError, AtlassianError, GoogleDocsError, SlackError, IntegrationError;
|
|
192
200
|
var init_errors = __esm({
|
|
193
201
|
"src/utils/errors.ts"() {
|
|
194
202
|
"use strict";
|
|
@@ -297,6 +305,12 @@ var init_errors = __esm({
|
|
|
297
305
|
this.name = "SlackError";
|
|
298
306
|
}
|
|
299
307
|
};
|
|
308
|
+
IntegrationError = class extends BragduckError {
|
|
309
|
+
constructor(message, details) {
|
|
310
|
+
super(message, "INTEGRATION_ERROR", details);
|
|
311
|
+
this.name = "IntegrationError";
|
|
312
|
+
}
|
|
313
|
+
};
|
|
300
314
|
}
|
|
301
315
|
});
|
|
302
316
|
|
|
@@ -1295,8 +1309,8 @@ __export(version_exports, {
|
|
|
1295
1309
|
import { readFileSync as readFileSync6 } from "fs";
|
|
1296
1310
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
1297
1311
|
import { dirname as dirname6, join as join8 } from "path";
|
|
1298
|
-
import
|
|
1299
|
-
import
|
|
1312
|
+
import chalk2 from "chalk";
|
|
1313
|
+
import boxen from "boxen";
|
|
1300
1314
|
function getCurrentVersion() {
|
|
1301
1315
|
try {
|
|
1302
1316
|
const packageJsonPath2 = join8(__dirname7, "../../package.json");
|
|
@@ -1356,16 +1370,16 @@ async function checkForUpdates(options = {}) {
|
|
|
1356
1370
|
}
|
|
1357
1371
|
}
|
|
1358
1372
|
function displayUpdateNotification(currentVersion, latestVersion, critical = false) {
|
|
1359
|
-
const urgency = critical ?
|
|
1373
|
+
const urgency = critical ? chalk2.red.bold("CRITICAL UPDATE") : chalk2.yellow.bold("Update Available");
|
|
1360
1374
|
const message = `${urgency}
|
|
1361
1375
|
|
|
1362
|
-
Current version: ${
|
|
1363
|
-
Latest version: ${
|
|
1376
|
+
Current version: ${chalk2.dim(currentVersion)}
|
|
1377
|
+
Latest version: ${chalk2.green(latestVersion)}
|
|
1364
1378
|
|
|
1365
|
-
Update with: ${
|
|
1379
|
+
Update with: ${chalk2.cyan("npm install -g @bragduck/cli@latest")}`;
|
|
1366
1380
|
console.log("");
|
|
1367
1381
|
console.log(
|
|
1368
|
-
|
|
1382
|
+
boxen(message, {
|
|
1369
1383
|
padding: 1,
|
|
1370
1384
|
margin: { top: 0, right: 1, bottom: 0, left: 1 },
|
|
1371
1385
|
borderStyle: "round",
|
|
@@ -1417,7 +1431,7 @@ function getPlatformInfo() {
|
|
|
1417
1431
|
const arch = process.arch;
|
|
1418
1432
|
return `${platform}-${arch}`;
|
|
1419
1433
|
}
|
|
1420
|
-
var __filename8, __dirname8, ApiService, apiService;
|
|
1434
|
+
var __filename8, __dirname8, REFINE_BATCH_SIZE, CREATE_BATCH_SIZE, inBatches, ApiService, apiService;
|
|
1421
1435
|
var init_api_service = __esm({
|
|
1422
1436
|
"src/services/api.service.ts"() {
|
|
1423
1437
|
"use strict";
|
|
@@ -1428,6 +1442,15 @@ var init_api_service = __esm({
|
|
|
1428
1442
|
init_logger();
|
|
1429
1443
|
__filename8 = fileURLToPath8(import.meta.url);
|
|
1430
1444
|
__dirname8 = dirname7(__filename8);
|
|
1445
|
+
REFINE_BATCH_SIZE = 20;
|
|
1446
|
+
CREATE_BATCH_SIZE = 50;
|
|
1447
|
+
inBatches = (items, size) => {
|
|
1448
|
+
const batches = [];
|
|
1449
|
+
for (let start = 0; start < items.length; start += size) {
|
|
1450
|
+
batches.push(items.slice(start, start + size));
|
|
1451
|
+
}
|
|
1452
|
+
return batches;
|
|
1453
|
+
};
|
|
1431
1454
|
ApiService = class {
|
|
1432
1455
|
baseURL;
|
|
1433
1456
|
client;
|
|
@@ -1543,10 +1566,23 @@ var init_api_service = __esm({
|
|
|
1543
1566
|
};
|
|
1544
1567
|
logger.debug(`Refining ${trimmedRequest.brags.length} brags`);
|
|
1545
1568
|
try {
|
|
1546
|
-
const response =
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1569
|
+
const response = {
|
|
1570
|
+
refined_brags: [],
|
|
1571
|
+
processing_time_ms: 0,
|
|
1572
|
+
tokens_used: 0
|
|
1573
|
+
};
|
|
1574
|
+
for (const batch of inBatches(trimmedRequest.brags, REFINE_BATCH_SIZE)) {
|
|
1575
|
+
const batchResponse = await this.makeRequest(
|
|
1576
|
+
API_ENDPOINTS.BRAGS.REFINE,
|
|
1577
|
+
{
|
|
1578
|
+
method: "POST",
|
|
1579
|
+
body: { ...trimmedRequest, brags: batch }
|
|
1580
|
+
}
|
|
1581
|
+
);
|
|
1582
|
+
response.refined_brags.push(...batchResponse.refined_brags);
|
|
1583
|
+
response.processing_time_ms += batchResponse.processing_time_ms;
|
|
1584
|
+
response.tokens_used += batchResponse.tokens_used;
|
|
1585
|
+
}
|
|
1550
1586
|
logger.debug(`Successfully refined ${response.refined_brags.length} brags`);
|
|
1551
1587
|
return response;
|
|
1552
1588
|
} catch (_error) {
|
|
@@ -1580,10 +1616,18 @@ var init_api_service = __esm({
|
|
|
1580
1616
|
logger.debug(`Creating ${request.brags.length} brags`);
|
|
1581
1617
|
logger.debug(`Request body: ${JSON.stringify(request, null, 2)}`);
|
|
1582
1618
|
try {
|
|
1583
|
-
const response =
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1619
|
+
const response = { created: 0, brags: [] };
|
|
1620
|
+
for (const batch of inBatches(request.brags, CREATE_BATCH_SIZE)) {
|
|
1621
|
+
const batchResponse = await this.makeRequest(
|
|
1622
|
+
API_ENDPOINTS.BRAGS.CREATE,
|
|
1623
|
+
{
|
|
1624
|
+
method: "POST",
|
|
1625
|
+
body: { ...request, brags: batch }
|
|
1626
|
+
}
|
|
1627
|
+
);
|
|
1628
|
+
response.created += batchResponse.created;
|
|
1629
|
+
response.brags.push(...batchResponse.brags);
|
|
1630
|
+
}
|
|
1587
1631
|
logger.debug(`Response: ${JSON.stringify(response, null, 2)}`);
|
|
1588
1632
|
logger.debug(`Successfully created ${response.created} brags`);
|
|
1589
1633
|
return response;
|
|
@@ -1622,6 +1666,70 @@ var init_api_service = __esm({
|
|
|
1622
1666
|
throw _error;
|
|
1623
1667
|
}
|
|
1624
1668
|
}
|
|
1669
|
+
/**
|
|
1670
|
+
* List user's stories
|
|
1671
|
+
*/
|
|
1672
|
+
async listStories(userId, params = {}) {
|
|
1673
|
+
const { page = 1, perPage = 20, search, competency, orgId } = params;
|
|
1674
|
+
logger.debug(`Listing stories: page=${page}, perPage=${perPage}`);
|
|
1675
|
+
try {
|
|
1676
|
+
const queryParams = new URLSearchParams6({
|
|
1677
|
+
page: page.toString(),
|
|
1678
|
+
perPage: perPage.toString()
|
|
1679
|
+
});
|
|
1680
|
+
if (search) queryParams.append("search", search);
|
|
1681
|
+
if (competency) queryParams.append("competency", competency);
|
|
1682
|
+
if (orgId) queryParams.append("orgId", orgId);
|
|
1683
|
+
const url = `${API_ENDPOINTS.STORIES.LIST.replace("{userId}", userId)}?${queryParams.toString()}`;
|
|
1684
|
+
const response = await this.makeRequest(url, {
|
|
1685
|
+
method: "GET"
|
|
1686
|
+
});
|
|
1687
|
+
logger.debug(
|
|
1688
|
+
`Successfully fetched ${response.items.length} stories (total: ${response.total})`
|
|
1689
|
+
);
|
|
1690
|
+
return response;
|
|
1691
|
+
} catch (_error) {
|
|
1692
|
+
logger.debug("Failed to list stories");
|
|
1693
|
+
throw _error;
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
/**
|
|
1697
|
+
* Create a story
|
|
1698
|
+
*/
|
|
1699
|
+
async createStory(userId, body) {
|
|
1700
|
+
logger.debug(`Creating story: ${body.title}`);
|
|
1701
|
+
try {
|
|
1702
|
+
const url = API_ENDPOINTS.STORIES.CREATE.replace("{userId}", userId);
|
|
1703
|
+
const response = await this.makeRequest(url, {
|
|
1704
|
+
method: "POST",
|
|
1705
|
+
body
|
|
1706
|
+
});
|
|
1707
|
+
logger.debug(`Successfully created story: ${response.id}`);
|
|
1708
|
+
return response;
|
|
1709
|
+
} catch (_error) {
|
|
1710
|
+
logger.debug("Failed to create story");
|
|
1711
|
+
throw _error;
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
/**
|
|
1715
|
+
* Delete a story (soft delete)
|
|
1716
|
+
*/
|
|
1717
|
+
async deleteStory(userId, storyId) {
|
|
1718
|
+
logger.debug(`Deleting story: ${storyId}`);
|
|
1719
|
+
try {
|
|
1720
|
+
const url = API_ENDPOINTS.STORIES.DELETE.replace("{userId}", userId).replace(
|
|
1721
|
+
"{storyId}",
|
|
1722
|
+
storyId
|
|
1723
|
+
);
|
|
1724
|
+
await this.makeRequest(url, {
|
|
1725
|
+
method: "DELETE"
|
|
1726
|
+
});
|
|
1727
|
+
logger.debug(`Successfully deleted story: ${storyId}`);
|
|
1728
|
+
} catch (_error) {
|
|
1729
|
+
logger.debug("Failed to delete story");
|
|
1730
|
+
throw _error;
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1625
1733
|
/**
|
|
1626
1734
|
* List user's organisations
|
|
1627
1735
|
*/
|
|
@@ -1660,6 +1768,18 @@ var init_api_service = __esm({
|
|
|
1660
1768
|
throw error;
|
|
1661
1769
|
}
|
|
1662
1770
|
}
|
|
1771
|
+
/**
|
|
1772
|
+
* Get the access token of a tool connected on the BragDuck web app.
|
|
1773
|
+
* The server refreshes it; error statuses are not retried.
|
|
1774
|
+
*/
|
|
1775
|
+
async getIntegrationAccessToken(provider) {
|
|
1776
|
+
logger.debug(`Fetching web-connected ${provider} access token`);
|
|
1777
|
+
const url = API_ENDPOINTS.INTEGRATIONS.ACCESS_TOKEN.replace("{provider}", provider);
|
|
1778
|
+
return this.makeRequest(url, {
|
|
1779
|
+
method: "GET",
|
|
1780
|
+
retry: 0
|
|
1781
|
+
});
|
|
1782
|
+
}
|
|
1663
1783
|
/**
|
|
1664
1784
|
* Check for CLI updates
|
|
1665
1785
|
*/
|
|
@@ -1721,8 +1841,8 @@ import { dirname as dirname8, join as join10 } from "path";
|
|
|
1721
1841
|
// src/commands/auth.ts
|
|
1722
1842
|
init_esm_shims();
|
|
1723
1843
|
init_auth_service();
|
|
1724
|
-
import
|
|
1725
|
-
import
|
|
1844
|
+
import boxen2 from "boxen";
|
|
1845
|
+
import chalk4 from "chalk";
|
|
1726
1846
|
import { input } from "@inquirer/prompts";
|
|
1727
1847
|
|
|
1728
1848
|
// src/services/atlassian-auth.service.ts
|
|
@@ -2560,20 +2680,123 @@ var GitService = class {
|
|
|
2560
2680
|
};
|
|
2561
2681
|
var gitService = new GitService();
|
|
2562
2682
|
|
|
2683
|
+
// src/services/integrations.service.ts
|
|
2684
|
+
init_esm_shims();
|
|
2685
|
+
init_api_service();
|
|
2686
|
+
init_auth_service();
|
|
2687
|
+
init_storage_service();
|
|
2688
|
+
init_errors();
|
|
2689
|
+
init_logger();
|
|
2690
|
+
var WEB_APP_HOST = "app.bragduck.com";
|
|
2691
|
+
var EXPIRY_MARGIN_MS = 60 * 1e3;
|
|
2692
|
+
var PROVIDER_LABELS = {
|
|
2693
|
+
atlassian: "Atlassian",
|
|
2694
|
+
github: "GitHub",
|
|
2695
|
+
slack: "Slack",
|
|
2696
|
+
google: "Google Docs"
|
|
2697
|
+
};
|
|
2698
|
+
function toHost(url) {
|
|
2699
|
+
return url.trim().toLowerCase().replace(/^https?:\/\//, "").replace(/\/+$/, "");
|
|
2700
|
+
}
|
|
2701
|
+
var IntegrationsService = class {
|
|
2702
|
+
cache = /* @__PURE__ */ new Map();
|
|
2703
|
+
loggedSites = /* @__PURE__ */ new Set();
|
|
2704
|
+
/**
|
|
2705
|
+
* Get the access token for a web-connected tool.
|
|
2706
|
+
* Returns null when the user is not logged in to BragDuck or the tool is not connected.
|
|
2707
|
+
*/
|
|
2708
|
+
async getConnectedAccessToken(provider) {
|
|
2709
|
+
const cached = this.cache.get(provider);
|
|
2710
|
+
if (cached && this.isFresh(cached)) {
|
|
2711
|
+
return cached;
|
|
2712
|
+
}
|
|
2713
|
+
if (!await authService.isAuthenticated()) {
|
|
2714
|
+
return null;
|
|
2715
|
+
}
|
|
2716
|
+
const label = PROVIDER_LABELS[provider];
|
|
2717
|
+
try {
|
|
2718
|
+
const connection = await apiService.getIntegrationAccessToken(provider);
|
|
2719
|
+
this.cache.set(provider, connection);
|
|
2720
|
+
logger.debug(`Using ${label} connected via web (${connection.accountName})`);
|
|
2721
|
+
return connection;
|
|
2722
|
+
} catch (error) {
|
|
2723
|
+
if (!(error instanceof ApiError)) {
|
|
2724
|
+
throw error;
|
|
2725
|
+
}
|
|
2726
|
+
if (error.statusCode === 404) {
|
|
2727
|
+
logger.debug(`${label} is not connected on the web`);
|
|
2728
|
+
return null;
|
|
2729
|
+
}
|
|
2730
|
+
if (error.statusCode === 409) {
|
|
2731
|
+
throw new IntegrationError(
|
|
2732
|
+
`Your ${label} connection has expired. Reconnect ${label} at ${WEB_APP_HOST}`,
|
|
2733
|
+
{ hint: `Reconnect ${label} at https://${WEB_APP_HOST}` }
|
|
2734
|
+
);
|
|
2735
|
+
}
|
|
2736
|
+
if (error.statusCode === 502) {
|
|
2737
|
+
throw new IntegrationError(`Could not refresh your ${label} token. Try again shortly.`, {
|
|
2738
|
+
hint: `If this keeps happening, reconnect ${label} at https://${WEB_APP_HOST}`
|
|
2739
|
+
});
|
|
2740
|
+
}
|
|
2741
|
+
throw error;
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
/**
|
|
2745
|
+
* Build Jira/Confluence OAuth credentials from the web-connected Atlassian account.
|
|
2746
|
+
* Uses the site matching the configured instance, otherwise the first site.
|
|
2747
|
+
*/
|
|
2748
|
+
async getAtlassianCredentials(service) {
|
|
2749
|
+
const connection = await this.getConnectedAccessToken("atlassian");
|
|
2750
|
+
const sites = connection?.metadata.sites ?? [];
|
|
2751
|
+
if (!connection || sites.length === 0) {
|
|
2752
|
+
return null;
|
|
2753
|
+
}
|
|
2754
|
+
const configuredInstance = await storageService.getConfigWithHierarchy(`${service}Instance`);
|
|
2755
|
+
const matchingSite = configuredInstance ? sites.find((s) => toHost(s.url) === toHost(configuredInstance)) : void 0;
|
|
2756
|
+
const site = matchingSite ?? sites[0];
|
|
2757
|
+
if (sites.length > 1 && !this.loggedSites.has(site.id)) {
|
|
2758
|
+
this.loggedSites.add(site.id);
|
|
2759
|
+
logger.info(`Using Atlassian site ${site.name} (${site.url}) connected via web`);
|
|
2760
|
+
}
|
|
2761
|
+
return {
|
|
2762
|
+
accessToken: connection.accessToken,
|
|
2763
|
+
cloudId: site.id,
|
|
2764
|
+
instanceUrl: site.url,
|
|
2765
|
+
authMethod: "oauth"
|
|
2766
|
+
};
|
|
2767
|
+
}
|
|
2768
|
+
isFresh(connection) {
|
|
2769
|
+
if (!connection.expiresAt) {
|
|
2770
|
+
return true;
|
|
2771
|
+
}
|
|
2772
|
+
return new Date(connection.expiresAt).getTime() - EXPIRY_MARGIN_MS > Date.now();
|
|
2773
|
+
}
|
|
2774
|
+
};
|
|
2775
|
+
var integrationsService = new IntegrationsService();
|
|
2776
|
+
|
|
2563
2777
|
// src/services/github.service.ts
|
|
2564
2778
|
var execAsync2 = promisify2(exec2);
|
|
2565
2779
|
var GitHubService = class {
|
|
2566
2780
|
MAX_BODY_LENGTH = 5e3;
|
|
2567
2781
|
PR_SEARCH_FIELDS = "number,title,body,author,mergedAt,additions,deletions,changedFiles,url,labels";
|
|
2782
|
+
// Set when gh has no login of its own and GitHub is connected on the BragDuck web app
|
|
2783
|
+
useWebConnection = false;
|
|
2568
2784
|
/**
|
|
2569
2785
|
* Execute a gh CLI command with clean environment
|
|
2570
2786
|
* Unsets GITHUB_TOKEN and GH_TOKEN to prevent .envrc tokens from interfering
|
|
2571
|
-
* with gh CLI's own authentication
|
|
2787
|
+
* with gh CLI's own authentication. When using the web connection, GH_TOKEN
|
|
2788
|
+
* is set to the server-held token instead.
|
|
2572
2789
|
*/
|
|
2573
2790
|
async execGhCommand(command, cwd) {
|
|
2574
2791
|
const env = { ...process.env };
|
|
2575
2792
|
delete env.GITHUB_TOKEN;
|
|
2576
2793
|
delete env.GH_TOKEN;
|
|
2794
|
+
if (this.useWebConnection) {
|
|
2795
|
+
const connection = await integrationsService.getConnectedAccessToken("github");
|
|
2796
|
+
if (connection) {
|
|
2797
|
+
env.GH_TOKEN = connection.accessToken;
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2577
2800
|
return execAsync2(command, { env, cwd: cwd || process.cwd() });
|
|
2578
2801
|
}
|
|
2579
2802
|
/**
|
|
@@ -2599,14 +2822,30 @@ var GitHubService = class {
|
|
|
2599
2822
|
}
|
|
2600
2823
|
}
|
|
2601
2824
|
/**
|
|
2602
|
-
* Check if user is authenticated with GitHub CLI
|
|
2825
|
+
* Check if user is authenticated with GitHub CLI, falling back to
|
|
2826
|
+
* the GitHub account connected on the BragDuck web app
|
|
2603
2827
|
*/
|
|
2604
2828
|
async checkAuthentication() {
|
|
2605
2829
|
try {
|
|
2606
2830
|
await this.execGhCommand("command gh auth status");
|
|
2607
2831
|
return true;
|
|
2608
2832
|
} catch {
|
|
2609
|
-
return
|
|
2833
|
+
return this.checkWebConnection();
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
/**
|
|
2837
|
+
* Use the server-held GitHub token when GitHub is connected on the web
|
|
2838
|
+
*/
|
|
2839
|
+
async checkWebConnection() {
|
|
2840
|
+
try {
|
|
2841
|
+
const connection = await integrationsService.getConnectedAccessToken("github");
|
|
2842
|
+
this.useWebConnection = connection !== null;
|
|
2843
|
+
return this.useWebConnection;
|
|
2844
|
+
} catch (error) {
|
|
2845
|
+
if (error instanceof IntegrationError) {
|
|
2846
|
+
throw new GitHubError(error.message, error.details);
|
|
2847
|
+
}
|
|
2848
|
+
throw error;
|
|
2610
2849
|
}
|
|
2611
2850
|
}
|
|
2612
2851
|
/**
|
|
@@ -2630,6 +2869,9 @@ var GitHubService = class {
|
|
|
2630
2869
|
return { installed: false, authenticated: false };
|
|
2631
2870
|
}
|
|
2632
2871
|
const authenticated = await this.checkAuthentication();
|
|
2872
|
+
if (this.useWebConnection) {
|
|
2873
|
+
return { installed, authenticated, connectedViaWeb: true };
|
|
2874
|
+
}
|
|
2633
2875
|
return { installed, authenticated };
|
|
2634
2876
|
}
|
|
2635
2877
|
/**
|
|
@@ -2804,33 +3046,33 @@ init_logger();
|
|
|
2804
3046
|
|
|
2805
3047
|
// src/ui/theme.ts
|
|
2806
3048
|
init_esm_shims();
|
|
2807
|
-
import
|
|
3049
|
+
import chalk3 from "chalk";
|
|
2808
3050
|
var colors = {
|
|
2809
3051
|
// Primary colors for main actions and interactive elements
|
|
2810
|
-
primary:
|
|
3052
|
+
primary: chalk3.cyan,
|
|
2811
3053
|
// Success states
|
|
2812
|
-
success:
|
|
2813
|
-
successBold:
|
|
3054
|
+
success: chalk3.green,
|
|
3055
|
+
successBold: chalk3.green.bold,
|
|
2814
3056
|
// Warning states
|
|
2815
|
-
warning:
|
|
2816
|
-
warningBold:
|
|
3057
|
+
warning: chalk3.yellow,
|
|
3058
|
+
warningBold: chalk3.yellow.bold,
|
|
2817
3059
|
// Error states
|
|
2818
|
-
error:
|
|
2819
|
-
errorBold:
|
|
3060
|
+
error: chalk3.red,
|
|
3061
|
+
errorBold: chalk3.red.bold,
|
|
2820
3062
|
// Info and metadata
|
|
2821
|
-
info:
|
|
2822
|
-
infoDim:
|
|
3063
|
+
info: chalk3.gray,
|
|
3064
|
+
infoDim: chalk3.dim,
|
|
2823
3065
|
// Highlighted/important data
|
|
2824
|
-
highlight:
|
|
2825
|
-
highlightCyan:
|
|
3066
|
+
highlight: chalk3.yellow.bold,
|
|
3067
|
+
highlightCyan: chalk3.cyan.bold,
|
|
2826
3068
|
// Text emphasis
|
|
2827
|
-
bold:
|
|
2828
|
-
dim:
|
|
3069
|
+
bold: chalk3.bold,
|
|
3070
|
+
dim: chalk3.dim,
|
|
2829
3071
|
// Special purpose
|
|
2830
|
-
white:
|
|
2831
|
-
gray:
|
|
3072
|
+
white: chalk3.white,
|
|
3073
|
+
gray: chalk3.gray,
|
|
2832
3074
|
// Links and URLs
|
|
2833
|
-
link:
|
|
3075
|
+
link: chalk3.blue.underline
|
|
2834
3076
|
};
|
|
2835
3077
|
var theme = {
|
|
2836
3078
|
/**
|
|
@@ -2999,13 +3241,13 @@ async function authLogin() {
|
|
|
2999
3241
|
const userInfo = authService.getUserInfo();
|
|
3000
3242
|
if (userInfo) {
|
|
3001
3243
|
logger.log(
|
|
3002
|
-
|
|
3003
|
-
`${
|
|
3244
|
+
boxen2(
|
|
3245
|
+
`${chalk4.yellow("Already authenticated!")}
|
|
3004
3246
|
|
|
3005
|
-
${
|
|
3006
|
-
${
|
|
3247
|
+
${chalk4.gray("User:")} ${userInfo.name}
|
|
3248
|
+
${chalk4.gray("Email:")} ${userInfo.email}
|
|
3007
3249
|
|
|
3008
|
-
${
|
|
3250
|
+
${chalk4.dim("Run")} ${chalk4.cyan("bragduck logout")} ${chalk4.dim("to sign out")}`,
|
|
3009
3251
|
{
|
|
3010
3252
|
padding: 1,
|
|
3011
3253
|
margin: 1,
|
|
@@ -3022,13 +3264,13 @@ ${chalk3.dim("Run")} ${chalk3.cyan("bragduck logout")} ${chalk3.dim("to sign out
|
|
|
3022
3264
|
const userInfo = await authService.login();
|
|
3023
3265
|
logger.log("");
|
|
3024
3266
|
logger.log(
|
|
3025
|
-
|
|
3026
|
-
`${
|
|
3267
|
+
boxen2(
|
|
3268
|
+
`${chalk4.green.bold("\u2713 Successfully authenticated!")}
|
|
3027
3269
|
|
|
3028
|
-
${
|
|
3029
|
-
${
|
|
3270
|
+
${chalk4.gray("Welcome,")} ${chalk4.cyan(userInfo.name)}
|
|
3271
|
+
${chalk4.gray("Email:")} ${userInfo.email}
|
|
3030
3272
|
|
|
3031
|
-
${
|
|
3273
|
+
${chalk4.dim("Use")} ${chalk4.cyan("bragduck sync")} ${chalk4.dim("to create brags")}`,
|
|
3032
3274
|
{
|
|
3033
3275
|
padding: 1,
|
|
3034
3276
|
margin: 1,
|
|
@@ -3042,7 +3284,7 @@ ${chalk3.dim("Use")} ${chalk3.cyan("bragduck sync")} ${chalk3.dim("to create bra
|
|
|
3042
3284
|
const err = error;
|
|
3043
3285
|
logger.log("");
|
|
3044
3286
|
logger.log(
|
|
3045
|
-
|
|
3287
|
+
boxen2(`${chalk4.red.bold("\u2717 Authentication Failed")}
|
|
3046
3288
|
|
|
3047
3289
|
${err.message}`, {
|
|
3048
3290
|
padding: 1,
|
|
@@ -3054,6 +3296,12 @@ ${err.message}`, {
|
|
|
3054
3296
|
process.exit(1);
|
|
3055
3297
|
}
|
|
3056
3298
|
}
|
|
3299
|
+
async function getWebConnection(provider) {
|
|
3300
|
+
return integrationsService.getConnectedAccessToken(provider).catch((error) => {
|
|
3301
|
+
logger.debug(`Could not check web-connected ${provider}: ${error}`);
|
|
3302
|
+
return null;
|
|
3303
|
+
});
|
|
3304
|
+
}
|
|
3057
3305
|
async function authStatus() {
|
|
3058
3306
|
logger.log("");
|
|
3059
3307
|
logger.info("Authentication Status:");
|
|
@@ -3069,9 +3317,14 @@ async function authStatus() {
|
|
|
3069
3317
|
logger.log("");
|
|
3070
3318
|
logger.info("Git Sources:");
|
|
3071
3319
|
logger.log("");
|
|
3072
|
-
const ghStatus = await githubService.getAuthStatus()
|
|
3320
|
+
const ghStatus = await githubService.getAuthStatus().catch((error) => {
|
|
3321
|
+
logger.debug(`Could not check GitHub auth status: ${error}`);
|
|
3322
|
+
return { installed: true, authenticated: false, connectedViaWeb: false };
|
|
3323
|
+
});
|
|
3073
3324
|
if (ghStatus.installed) {
|
|
3074
|
-
if (ghStatus.
|
|
3325
|
+
if (ghStatus.connectedViaWeb) {
|
|
3326
|
+
logger.info(`${colors.success("\u2713")} GitHub: connected via web`);
|
|
3327
|
+
} else if (ghStatus.authenticated) {
|
|
3075
3328
|
logger.info(`${colors.success("\u2713")} GitHub: Authenticated`);
|
|
3076
3329
|
} else {
|
|
3077
3330
|
logger.info(`${colors.error("\u2717")} GitHub: Not authenticated`);
|
|
@@ -3116,8 +3369,15 @@ async function authStatus() {
|
|
|
3116
3369
|
if (jiraAuth) logger.info(theme.secondary(" \xB7 Jira"));
|
|
3117
3370
|
if (confluenceAuth) logger.info(theme.secondary(" \xB7 Confluence"));
|
|
3118
3371
|
} else {
|
|
3119
|
-
|
|
3120
|
-
|
|
3372
|
+
const webAtlassian = await getWebConnection("atlassian");
|
|
3373
|
+
if (webAtlassian) {
|
|
3374
|
+
logger.info(
|
|
3375
|
+
`${colors.success("\u2713")} Jira/Confluence: connected via web (${webAtlassian.accountName})`
|
|
3376
|
+
);
|
|
3377
|
+
} else {
|
|
3378
|
+
logger.info(`${colors.error("\u2717")} Atlassian (Jira + Confluence): Not connected`);
|
|
3379
|
+
logger.info(theme.secondary(" Run: bragduck auth atlassian"));
|
|
3380
|
+
}
|
|
3121
3381
|
}
|
|
3122
3382
|
logger.log("");
|
|
3123
3383
|
logger.info("Document Sources:");
|
|
@@ -3126,8 +3386,15 @@ async function authStatus() {
|
|
|
3126
3386
|
if (googleDocsAuth) {
|
|
3127
3387
|
logger.info(`${colors.success("\u2713")} Google Docs (OAuth)`);
|
|
3128
3388
|
} else {
|
|
3129
|
-
|
|
3130
|
-
|
|
3389
|
+
const webGoogle = await getWebConnection("google");
|
|
3390
|
+
if (webGoogle) {
|
|
3391
|
+
logger.info(
|
|
3392
|
+
`${colors.success("\u2713")} Google Docs: connected via web (${webGoogle.accountName})`
|
|
3393
|
+
);
|
|
3394
|
+
} else {
|
|
3395
|
+
logger.info(`${colors.error("\u2717")} Google Docs: Not connected`);
|
|
3396
|
+
logger.info(theme.secondary(" Run: bragduck auth google"));
|
|
3397
|
+
}
|
|
3131
3398
|
}
|
|
3132
3399
|
const slackAuth = await storageService.isServiceAuthenticated("slack");
|
|
3133
3400
|
if (slackAuth) {
|
|
@@ -3135,15 +3402,20 @@ async function authStatus() {
|
|
|
3135
3402
|
const userLabel = creds?.username ? ` (user: ${creds.username})` : "";
|
|
3136
3403
|
logger.info(`${colors.success("\u2713")} Slack: Connected${userLabel}`);
|
|
3137
3404
|
} else {
|
|
3138
|
-
|
|
3139
|
-
|
|
3405
|
+
const webSlack = await getWebConnection("slack");
|
|
3406
|
+
if (webSlack) {
|
|
3407
|
+
logger.info(`${colors.success("\u2713")} Slack: connected via web (${webSlack.accountName})`);
|
|
3408
|
+
} else {
|
|
3409
|
+
logger.info(`${colors.error("\u2717")} Slack: Not connected`);
|
|
3410
|
+
logger.info(theme.secondary(" Run: bragduck auth slack"));
|
|
3411
|
+
}
|
|
3140
3412
|
}
|
|
3141
3413
|
logger.log("");
|
|
3142
3414
|
}
|
|
3143
3415
|
async function authBitbucket() {
|
|
3144
3416
|
logger.log("");
|
|
3145
3417
|
logger.log(
|
|
3146
|
-
|
|
3418
|
+
boxen2(
|
|
3147
3419
|
theme.info("Bitbucket API Token Authentication") + "\n\nCreate an API Token at:\n" + colors.highlight("https://bitbucket.org/account/settings/api-token/new") + "\n\nRequired scopes:\n \u2022 pullrequest:read\n \u2022 repository:read\n \u2022 account:read\n\n" + theme.warning("Note: API tokens expire (max 1 year)"),
|
|
3148
3420
|
boxStyles.info
|
|
3149
3421
|
)
|
|
@@ -3174,7 +3446,7 @@ async function authBitbucket() {
|
|
|
3174
3446
|
if (!response.ok) {
|
|
3175
3447
|
logger.log("");
|
|
3176
3448
|
logger.log(
|
|
3177
|
-
|
|
3449
|
+
boxen2(
|
|
3178
3450
|
theme.error("\u2717 Authentication Failed") + "\n\nInvalid email or API token",
|
|
3179
3451
|
boxStyles.error
|
|
3180
3452
|
)
|
|
@@ -3195,7 +3467,7 @@ async function authBitbucket() {
|
|
|
3195
3467
|
await storageService.setServiceCredentials("bitbucket", credentials);
|
|
3196
3468
|
logger.log("");
|
|
3197
3469
|
logger.log(
|
|
3198
|
-
|
|
3470
|
+
boxen2(
|
|
3199
3471
|
theme.success("\u2713 Successfully authenticated with Bitbucket") + `
|
|
3200
3472
|
|
|
3201
3473
|
Email: ${email}
|
|
@@ -3209,7 +3481,7 @@ User: ${user.display_name}
|
|
|
3209
3481
|
const err = error;
|
|
3210
3482
|
logger.log("");
|
|
3211
3483
|
logger.log(
|
|
3212
|
-
|
|
3484
|
+
boxen2(
|
|
3213
3485
|
theme.error("\u2717 Authentication Failed") + "\n\n" + (err.message || "Unknown error"),
|
|
3214
3486
|
boxStyles.error
|
|
3215
3487
|
)
|
|
@@ -3221,7 +3493,7 @@ User: ${user.display_name}
|
|
|
3221
3493
|
async function authGitLab() {
|
|
3222
3494
|
logger.log("");
|
|
3223
3495
|
logger.log(
|
|
3224
|
-
|
|
3496
|
+
boxen2(
|
|
3225
3497
|
theme.info("GitLab Personal Access Token Authentication") + "\n\nCreate a Personal Access Token at:\n" + colors.highlight("https://gitlab.com/-/profile/personal_access_tokens") + "\n\nRequired scopes:\n \u2022 api (full API access)\n \u2022 read_api (read API)\n \u2022 read_user (read user info)\n\n" + theme.warning("For self-hosted GitLab, check your instance docs"),
|
|
3226
3498
|
boxStyles.info
|
|
3227
3499
|
)
|
|
@@ -3243,7 +3515,7 @@ async function authGitLab() {
|
|
|
3243
3515
|
if (!response.ok) {
|
|
3244
3516
|
logger.log("");
|
|
3245
3517
|
logger.log(
|
|
3246
|
-
|
|
3518
|
+
boxen2(
|
|
3247
3519
|
theme.error("\u2717 Authentication Failed") + "\n\nInvalid instance URL or access token",
|
|
3248
3520
|
boxStyles.error
|
|
3249
3521
|
)
|
|
@@ -3259,7 +3531,7 @@ async function authGitLab() {
|
|
|
3259
3531
|
await storageService.setServiceCredentials("gitlab", credentials);
|
|
3260
3532
|
logger.log("");
|
|
3261
3533
|
logger.log(
|
|
3262
|
-
|
|
3534
|
+
boxen2(
|
|
3263
3535
|
theme.success("\u2713 Successfully authenticated with GitLab") + `
|
|
3264
3536
|
|
|
3265
3537
|
Instance: ${instanceUrl}
|
|
@@ -3272,7 +3544,7 @@ User: ${user.name} (@${user.username})`,
|
|
|
3272
3544
|
const err = error;
|
|
3273
3545
|
logger.log("");
|
|
3274
3546
|
logger.log(
|
|
3275
|
-
|
|
3547
|
+
boxen2(
|
|
3276
3548
|
theme.error("\u2717 Authentication Failed") + "\n\n" + (err.message || "Unknown error"),
|
|
3277
3549
|
boxStyles.error
|
|
3278
3550
|
)
|
|
@@ -3289,7 +3561,7 @@ async function authAtlassian() {
|
|
|
3289
3561
|
const result = await atlassianAuthService.login();
|
|
3290
3562
|
logger.log("");
|
|
3291
3563
|
logger.log(
|
|
3292
|
-
|
|
3564
|
+
boxen2(
|
|
3293
3565
|
theme.success("\u2713 Successfully authenticated with Atlassian Cloud") + `
|
|
3294
3566
|
|
|
3295
3567
|
Site: ${result.siteName}
|
|
@@ -3306,7 +3578,7 @@ Services configured:
|
|
|
3306
3578
|
const err = error;
|
|
3307
3579
|
logger.log("");
|
|
3308
3580
|
logger.log(
|
|
3309
|
-
|
|
3581
|
+
boxen2(
|
|
3310
3582
|
theme.error("\u2717 Authentication Failed") + "\n\n" + (err.message || "Unknown error"),
|
|
3311
3583
|
boxStyles.error
|
|
3312
3584
|
)
|
|
@@ -3323,7 +3595,7 @@ async function authGoogle() {
|
|
|
3323
3595
|
const result = await googleAuthService.login();
|
|
3324
3596
|
logger.log("");
|
|
3325
3597
|
logger.log(
|
|
3326
|
-
|
|
3598
|
+
boxen2(
|
|
3327
3599
|
theme.success("\u2713 Successfully authenticated with Google") + `
|
|
3328
3600
|
|
|
3329
3601
|
Account: ${result.email}
|
|
@@ -3338,7 +3610,7 @@ Services configured:
|
|
|
3338
3610
|
const err = error;
|
|
3339
3611
|
logger.log("");
|
|
3340
3612
|
logger.log(
|
|
3341
|
-
|
|
3613
|
+
boxen2(
|
|
3342
3614
|
theme.error("\u2717 Authentication Failed") + "\n\n" + (err.message || "Unknown error"),
|
|
3343
3615
|
boxStyles.error
|
|
3344
3616
|
)
|
|
@@ -3355,7 +3627,7 @@ async function authSlack() {
|
|
|
3355
3627
|
const result = await slackAuthService.login();
|
|
3356
3628
|
logger.log("");
|
|
3357
3629
|
logger.log(
|
|
3358
|
-
|
|
3630
|
+
boxen2(
|
|
3359
3631
|
theme.success("\u2713 Successfully authenticated with Slack") + `
|
|
3360
3632
|
|
|
3361
3633
|
Workspace: ${result.teamName}
|
|
@@ -3370,7 +3642,7 @@ Services configured:
|
|
|
3370
3642
|
const err = error;
|
|
3371
3643
|
logger.log("");
|
|
3372
3644
|
logger.log(
|
|
3373
|
-
|
|
3645
|
+
boxen2(
|
|
3374
3646
|
theme.error("\u2717 Authentication Failed") + "\n\n" + (err.message || "Unknown error"),
|
|
3375
3647
|
boxStyles.error
|
|
3376
3648
|
)
|
|
@@ -3553,8 +3825,8 @@ var SourceDetector = class {
|
|
|
3553
3825
|
async checkAuthentication(type) {
|
|
3554
3826
|
try {
|
|
3555
3827
|
if (type === "github") {
|
|
3556
|
-
|
|
3557
|
-
return
|
|
3828
|
+
const { authenticated } = await githubService.getAuthStatus();
|
|
3829
|
+
return authenticated;
|
|
3558
3830
|
} else if (type === "bitbucket" || type === "atlassian") {
|
|
3559
3831
|
return await storageService.isServiceAuthenticated("bitbucket");
|
|
3560
3832
|
} else if (type === "gitlab") {
|
|
@@ -4303,6 +4575,10 @@ var JiraService = class {
|
|
|
4303
4575
|
async getCredentials() {
|
|
4304
4576
|
const creds = await storageService.getServiceCredentials("jira");
|
|
4305
4577
|
if (!creds || !creds.accessToken) {
|
|
4578
|
+
const webCreds = await integrationsService.getAtlassianCredentials("jira");
|
|
4579
|
+
if (webCreds) {
|
|
4580
|
+
return webCreds;
|
|
4581
|
+
}
|
|
4306
4582
|
throw new JiraError("Not authenticated with Jira", {
|
|
4307
4583
|
hint: "Run: bragduck auth atlassian"
|
|
4308
4584
|
});
|
|
@@ -4429,7 +4705,7 @@ var JiraService = class {
|
|
|
4429
4705
|
try {
|
|
4430
4706
|
await this.request("/rest/api/3/myself");
|
|
4431
4707
|
} catch (error) {
|
|
4432
|
-
if (error instanceof JiraError) {
|
|
4708
|
+
if (error instanceof JiraError || error instanceof IntegrationError) {
|
|
4433
4709
|
throw error;
|
|
4434
4710
|
}
|
|
4435
4711
|
throw new JiraError("Could not access Jira instance via API", {
|
|
@@ -4563,7 +4839,8 @@ var JiraService = class {
|
|
|
4563
4839
|
"reporter",
|
|
4564
4840
|
"assignee",
|
|
4565
4841
|
"status",
|
|
4566
|
-
"issuetype"
|
|
4842
|
+
"issuetype",
|
|
4843
|
+
"project"
|
|
4567
4844
|
];
|
|
4568
4845
|
const allIssues = [];
|
|
4569
4846
|
const seenIssueKeys = /* @__PURE__ */ new Set();
|
|
@@ -4740,7 +5017,8 @@ ${truncatedDesc}`;
|
|
|
4740
5017
|
externalId: issue.key,
|
|
4741
5018
|
externalType: contribution?.type || "issue",
|
|
4742
5019
|
externalSource: "jira",
|
|
4743
|
-
externalUrl: url
|
|
5020
|
+
externalUrl: url,
|
|
5021
|
+
sourceName: issue.fields.project?.name || issue.fields.project?.key
|
|
4744
5022
|
};
|
|
4745
5023
|
}
|
|
4746
5024
|
};
|
|
@@ -4802,6 +5080,10 @@ var ConfluenceService = class {
|
|
|
4802
5080
|
async getCredentials() {
|
|
4803
5081
|
const creds = await storageService.getServiceCredentials("confluence");
|
|
4804
5082
|
if (!creds || !creds.accessToken) {
|
|
5083
|
+
const webCreds = await integrationsService.getAtlassianCredentials("confluence");
|
|
5084
|
+
if (webCreds) {
|
|
5085
|
+
return webCreds;
|
|
5086
|
+
}
|
|
4805
5087
|
throw new ConfluenceError("Not authenticated with Confluence", {
|
|
4806
5088
|
hint: "Run: bragduck auth atlassian"
|
|
4807
5089
|
});
|
|
@@ -4922,7 +5204,7 @@ var ConfluenceService = class {
|
|
|
4922
5204
|
try {
|
|
4923
5205
|
await this.request("/wiki/rest/api/content?type=page&limit=1");
|
|
4924
5206
|
} catch (error) {
|
|
4925
|
-
if (error instanceof ConfluenceError) {
|
|
5207
|
+
if (error instanceof ConfluenceError || error instanceof IntegrationError) {
|
|
4926
5208
|
throw error;
|
|
4927
5209
|
}
|
|
4928
5210
|
throw new ConfluenceError("Could not access Confluence instance via API", {
|
|
@@ -5046,7 +5328,7 @@ var ConfluenceService = class {
|
|
|
5046
5328
|
cql,
|
|
5047
5329
|
start: start.toString(),
|
|
5048
5330
|
limit: limit.toString(),
|
|
5049
|
-
expand: "version,history,children.comment,body.storage"
|
|
5331
|
+
expand: "version,history,children.comment,body.storage,space"
|
|
5050
5332
|
};
|
|
5051
5333
|
const queryString = Object.entries(params).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&");
|
|
5052
5334
|
const endpoint = `/wiki/rest/api/content/search?${queryString}`;
|
|
@@ -5190,7 +5472,8 @@ ${contribution.details}
|
|
|
5190
5472
|
externalId: page.id,
|
|
5191
5473
|
externalType: contribution?.type || "page",
|
|
5192
5474
|
externalSource: "confluence",
|
|
5193
|
-
externalUrl: url
|
|
5475
|
+
externalUrl: url,
|
|
5476
|
+
sourceName: page.space?.name || page.space?.key
|
|
5194
5477
|
};
|
|
5195
5478
|
}
|
|
5196
5479
|
};
|
|
@@ -5251,6 +5534,10 @@ var GoogleDocsService = class {
|
|
|
5251
5534
|
async getCredentials() {
|
|
5252
5535
|
const creds = await storageService.getServiceCredentials("google-docs");
|
|
5253
5536
|
if (!creds || !creds.accessToken) {
|
|
5537
|
+
const connection = await integrationsService.getConnectedAccessToken("google");
|
|
5538
|
+
if (connection) {
|
|
5539
|
+
return { accessToken: connection.accessToken };
|
|
5540
|
+
}
|
|
5254
5541
|
throw new GoogleDocsError("Not authenticated with Google Docs", {
|
|
5255
5542
|
hint: "Run: bragduck auth google"
|
|
5256
5543
|
});
|
|
@@ -5349,7 +5636,7 @@ var GoogleDocsService = class {
|
|
|
5349
5636
|
"https://www.googleapis.com/drive/v3/about?fields=user"
|
|
5350
5637
|
);
|
|
5351
5638
|
} catch (error) {
|
|
5352
|
-
if (error instanceof GoogleDocsError) {
|
|
5639
|
+
if (error instanceof GoogleDocsError || error instanceof IntegrationError) {
|
|
5353
5640
|
throw error;
|
|
5354
5641
|
}
|
|
5355
5642
|
throw new GoogleDocsError("Could not access Google Drive API", {
|
|
@@ -5506,6 +5793,8 @@ init_logger();
|
|
|
5506
5793
|
init_storage_service();
|
|
5507
5794
|
import { URLSearchParams as URLSearchParams8 } from "url";
|
|
5508
5795
|
var SlackService = class {
|
|
5796
|
+
// Slack user ID of the web-connected account, looked up once per process
|
|
5797
|
+
webUserId = null;
|
|
5509
5798
|
/**
|
|
5510
5799
|
* Get stored Slack credentials.
|
|
5511
5800
|
* Slack user tokens (xoxp-) don't expire by default — no expiry check needed.
|
|
@@ -5513,12 +5802,39 @@ var SlackService = class {
|
|
|
5513
5802
|
async getCredentials() {
|
|
5514
5803
|
const creds = await storageService.getServiceCredentials("slack");
|
|
5515
5804
|
if (!creds || !creds.accessToken) {
|
|
5805
|
+
const webCreds = await this.getWebCredentials();
|
|
5806
|
+
if (webCreds) {
|
|
5807
|
+
return webCreds;
|
|
5808
|
+
}
|
|
5516
5809
|
throw new SlackError("Not authenticated with Slack", {
|
|
5517
5810
|
hint: "Run: bragduck auth slack"
|
|
5518
5811
|
});
|
|
5519
5812
|
}
|
|
5520
5813
|
return creds;
|
|
5521
5814
|
}
|
|
5815
|
+
/**
|
|
5816
|
+
* Build credentials from the Slack account connected on the BragDuck web app.
|
|
5817
|
+
* The server does not return the Slack user ID, so it is looked up via auth.test.
|
|
5818
|
+
*/
|
|
5819
|
+
async getWebCredentials() {
|
|
5820
|
+
const connection = await integrationsService.getConnectedAccessToken("slack");
|
|
5821
|
+
if (!connection) {
|
|
5822
|
+
return null;
|
|
5823
|
+
}
|
|
5824
|
+
if (!this.webUserId) {
|
|
5825
|
+
const data = await this.authTest(connection.accessToken);
|
|
5826
|
+
if (!data.ok) {
|
|
5827
|
+
throw new SlackError(`Slack credentials invalid: ${data.error || "unknown error"}`, {
|
|
5828
|
+
hint: "Reconnect Slack at https://app.bragduck.com"
|
|
5829
|
+
});
|
|
5830
|
+
}
|
|
5831
|
+
this.webUserId = data.user_id;
|
|
5832
|
+
}
|
|
5833
|
+
return {
|
|
5834
|
+
accessToken: connection.accessToken,
|
|
5835
|
+
username: this.webUserId
|
|
5836
|
+
};
|
|
5837
|
+
}
|
|
5522
5838
|
/**
|
|
5523
5839
|
* Make authenticated GET request to Slack Web API
|
|
5524
5840
|
*/
|
|
@@ -5563,18 +5879,24 @@ var SlackService = class {
|
|
|
5563
5879
|
return data;
|
|
5564
5880
|
}
|
|
5565
5881
|
/**
|
|
5566
|
-
*
|
|
5882
|
+
* Call Slack auth.test with the given token
|
|
5567
5883
|
*/
|
|
5568
|
-
async
|
|
5569
|
-
const creds = await this.getCredentials();
|
|
5884
|
+
async authTest(accessToken) {
|
|
5570
5885
|
const response = await fetch("https://slack.com/api/auth.test", {
|
|
5571
5886
|
method: "POST",
|
|
5572
5887
|
headers: {
|
|
5573
|
-
Authorization: `Bearer ${
|
|
5888
|
+
Authorization: `Bearer ${accessToken}`,
|
|
5574
5889
|
"Content-Type": "application/json"
|
|
5575
5890
|
}
|
|
5576
5891
|
});
|
|
5577
|
-
|
|
5892
|
+
return await response.json();
|
|
5893
|
+
}
|
|
5894
|
+
/**
|
|
5895
|
+
* Validate Slack credentials via auth.test
|
|
5896
|
+
*/
|
|
5897
|
+
async validateCredentials() {
|
|
5898
|
+
const creds = await this.getCredentials();
|
|
5899
|
+
const data = await this.authTest(creds.accessToken);
|
|
5578
5900
|
if (!data.ok) {
|
|
5579
5901
|
throw new SlackError(`Slack credentials invalid: ${data.error || "unknown error"}`, {
|
|
5580
5902
|
hint: "Run: bragduck auth slack"
|
|
@@ -5587,14 +5909,7 @@ var SlackService = class {
|
|
|
5587
5909
|
async getCurrentUser() {
|
|
5588
5910
|
try {
|
|
5589
5911
|
const creds = await this.getCredentials();
|
|
5590
|
-
const
|
|
5591
|
-
method: "POST",
|
|
5592
|
-
headers: {
|
|
5593
|
-
Authorization: `Bearer ${creds.accessToken}`,
|
|
5594
|
-
"Content-Type": "application/json"
|
|
5595
|
-
}
|
|
5596
|
-
});
|
|
5597
|
-
const data = await response.json();
|
|
5912
|
+
const data = await this.authTest(creds.accessToken);
|
|
5598
5913
|
return data.ok ? data.user : null;
|
|
5599
5914
|
} catch {
|
|
5600
5915
|
return null;
|
|
@@ -5667,7 +5982,9 @@ var SlackService = class {
|
|
|
5667
5982
|
externalId: msg.ts,
|
|
5668
5983
|
externalType: "message",
|
|
5669
5984
|
externalSource: "slack",
|
|
5670
|
-
externalUrl: msg.permalink
|
|
5985
|
+
externalUrl: msg.permalink,
|
|
5986
|
+
// Direct messages have no channel name to tag
|
|
5987
|
+
sourceName: msg.channel?.name || void 0
|
|
5671
5988
|
};
|
|
5672
5989
|
}
|
|
5673
5990
|
};
|
|
@@ -5746,6 +6063,22 @@ var AdapterFactory = class {
|
|
|
5746
6063
|
// src/commands/sync.ts
|
|
5747
6064
|
init_logger();
|
|
5748
6065
|
|
|
6066
|
+
// src/utils/external-source.ts
|
|
6067
|
+
init_esm_shims();
|
|
6068
|
+
function getGitExternalSource(source) {
|
|
6069
|
+
switch (source) {
|
|
6070
|
+
case "github":
|
|
6071
|
+
return "github";
|
|
6072
|
+
case "gitlab":
|
|
6073
|
+
return "gitlab";
|
|
6074
|
+
case "bitbucket":
|
|
6075
|
+
case "atlassian":
|
|
6076
|
+
return "bitbucket";
|
|
6077
|
+
default:
|
|
6078
|
+
return void 0;
|
|
6079
|
+
}
|
|
6080
|
+
}
|
|
6081
|
+
|
|
5749
6082
|
// src/utils/auth-helper.ts
|
|
5750
6083
|
init_esm_shims();
|
|
5751
6084
|
init_auth_service();
|
|
@@ -5970,6 +6303,12 @@ async function promptConfirm(message, defaultValue = true) {
|
|
|
5970
6303
|
default: defaultValue
|
|
5971
6304
|
});
|
|
5972
6305
|
}
|
|
6306
|
+
async function promptInput(message, defaultValue) {
|
|
6307
|
+
return await input2({
|
|
6308
|
+
message,
|
|
6309
|
+
default: defaultValue
|
|
6310
|
+
});
|
|
6311
|
+
}
|
|
5973
6312
|
async function promptDaysToScan(defaultDays = 30) {
|
|
5974
6313
|
const choices = [
|
|
5975
6314
|
{ name: "1 day", value: "1", description: "Last 24 hours" },
|
|
@@ -6217,6 +6556,13 @@ function createFetchSpinner(text) {
|
|
|
6217
6556
|
spinner: "bouncingBar"
|
|
6218
6557
|
});
|
|
6219
6558
|
}
|
|
6559
|
+
function createWriteSpinner(text) {
|
|
6560
|
+
return ora({
|
|
6561
|
+
text,
|
|
6562
|
+
color: "cyan",
|
|
6563
|
+
spinner: "dots"
|
|
6564
|
+
});
|
|
6565
|
+
}
|
|
6220
6566
|
function createValidateSpinner(text) {
|
|
6221
6567
|
return ora({
|
|
6222
6568
|
text,
|
|
@@ -6239,6 +6585,15 @@ function updateStepSpinner(spinner, currentStep, totalSteps, text) {
|
|
|
6239
6585
|
function fetchingBragsSpinner() {
|
|
6240
6586
|
return createFetchSpinner("Fetching your brags...");
|
|
6241
6587
|
}
|
|
6588
|
+
function fetchingStoriesSpinner() {
|
|
6589
|
+
return createFetchSpinner("Fetching your stories...");
|
|
6590
|
+
}
|
|
6591
|
+
function creatingStorySpinner() {
|
|
6592
|
+
return createWriteSpinner("Creating story...");
|
|
6593
|
+
}
|
|
6594
|
+
function deletingStorySpinner() {
|
|
6595
|
+
return createWriteSpinner("Deleting story...");
|
|
6596
|
+
}
|
|
6242
6597
|
function succeedSpinner(spinner, text) {
|
|
6243
6598
|
if (text) {
|
|
6244
6599
|
spinner.succeed(colors.success(text));
|
|
@@ -6381,6 +6736,17 @@ init_api_service();
|
|
|
6381
6736
|
init_auth_service();
|
|
6382
6737
|
init_storage_service();
|
|
6383
6738
|
init_logger();
|
|
6739
|
+
|
|
6740
|
+
// src/utils/source-tag.ts
|
|
6741
|
+
init_esm_shims();
|
|
6742
|
+
var MAX_SOURCE_TAG_LENGTH = 50;
|
|
6743
|
+
function withSourceTag(sourceName, tags) {
|
|
6744
|
+
const tag = sourceName?.replace(/,/g, " ").replace(/\s+/g, " ").trim().slice(0, MAX_SOURCE_TAG_LENGTH).trim();
|
|
6745
|
+
if (!tag) return tags;
|
|
6746
|
+
return [tag, ...tags.filter((existing) => existing.toLowerCase() !== tag.toLowerCase())];
|
|
6747
|
+
}
|
|
6748
|
+
|
|
6749
|
+
// src/sync/multi-repo-sync.ts
|
|
6384
6750
|
async function syncSingleRepository(repo, days, sortOption, scanMode, orgId, options) {
|
|
6385
6751
|
const TOTAL_STEPS = 5;
|
|
6386
6752
|
logger.debug(`Syncing repository: ${repo.name} at ${repo.path}`);
|
|
@@ -6535,10 +6901,10 @@ async function syncSingleRepository(repo, days, sortOption, scanMode, orgId, opt
|
|
|
6535
6901
|
);
|
|
6536
6902
|
createSpinner.start();
|
|
6537
6903
|
const createRequest = {
|
|
6904
|
+
mode: options.turbo ? "turbo" : "manual",
|
|
6538
6905
|
brags: acceptedBrags.map((refined, index) => {
|
|
6539
6906
|
const originalCommit = selectedCommits[index];
|
|
6540
|
-
const
|
|
6541
|
-
const tagsWithRepo = refined.suggested_tags.includes(repoTag) ? refined.suggested_tags : [repoTag, ...refined.suggested_tags];
|
|
6907
|
+
const tagsWithRepo = withSourceTag(repo.name, refined.suggested_tags);
|
|
6542
6908
|
return {
|
|
6543
6909
|
commit_sha: originalCommit?.sha || `brag-${index}`,
|
|
6544
6910
|
title: refined.refined_title,
|
|
@@ -6557,7 +6923,7 @@ async function syncSingleRepository(repo, days, sortOption, scanMode, orgId, opt
|
|
|
6557
6923
|
// External fields for non-git sources
|
|
6558
6924
|
externalId: originalCommit?.externalId,
|
|
6559
6925
|
externalType: originalCommit?.externalType,
|
|
6560
|
-
externalSource: originalCommit?.externalSource,
|
|
6926
|
+
externalSource: originalCommit?.externalSource ?? getGitExternalSource(repo.service),
|
|
6561
6927
|
externalUrl: originalCommit?.externalUrl
|
|
6562
6928
|
};
|
|
6563
6929
|
})
|
|
@@ -6963,14 +7329,14 @@ async function syncSingleService(sourceType, options, TOTAL_STEPS, sharedDays, s
|
|
|
6963
7329
|
);
|
|
6964
7330
|
createSpinner.start();
|
|
6965
7331
|
const createRequest = {
|
|
7332
|
+
mode: options.turbo ? "turbo" : "manual",
|
|
6966
7333
|
brags: acceptedBrags.map((refined, index) => {
|
|
6967
7334
|
const originalCommit = selectedCommits[index];
|
|
6968
7335
|
const isGitSource = sourceType === "github" || sourceType === "gitlab" || sourceType === "bitbucket" || sourceType === "atlassian";
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
|
|
6972
|
-
|
|
6973
|
-
}
|
|
7336
|
+
const tagsWithRepo = withSourceTag(
|
|
7337
|
+
isGitSource ? repoInfo.name : originalCommit?.sourceName,
|
|
7338
|
+
refined.suggested_tags
|
|
7339
|
+
);
|
|
6974
7340
|
return {
|
|
6975
7341
|
commit_sha: originalCommit?.sha || `brag-${index}`,
|
|
6976
7342
|
title: refined.refined_title,
|
|
@@ -6988,7 +7354,7 @@ async function syncSingleService(sourceType, options, TOTAL_STEPS, sharedDays, s
|
|
|
6988
7354
|
// External fields for non-git sources (Jira, Confluence, etc.)
|
|
6989
7355
|
externalId: originalCommit?.externalId,
|
|
6990
7356
|
externalType: originalCommit?.externalType,
|
|
6991
|
-
externalSource: originalCommit?.externalSource,
|
|
7357
|
+
externalSource: originalCommit?.externalSource ?? getGitExternalSource(sourceType),
|
|
6992
7358
|
externalUrl: originalCommit?.externalUrl
|
|
6993
7359
|
};
|
|
6994
7360
|
})
|
|
@@ -7368,7 +7734,8 @@ To authenticate:
|
|
|
7368
7734
|
sourceType = selectedSource;
|
|
7369
7735
|
}
|
|
7370
7736
|
if (sourceType === "jira" || sourceType === "confluence") {
|
|
7371
|
-
const
|
|
7737
|
+
const localCreds = await storageService.getServiceCredentials(sourceType);
|
|
7738
|
+
const creds = localCreds?.accessToken ? localCreds : await integrationsService.getAtlassianCredentials(sourceType);
|
|
7372
7739
|
const envInstance = loadEnvConfig()[`${sourceType}Instance`];
|
|
7373
7740
|
const projectConfig = await findProjectConfig();
|
|
7374
7741
|
const configInstance = projectConfig?.[`${sourceType}Instance`];
|
|
@@ -7471,6 +7838,9 @@ function getErrorHint(error, sourceType) {
|
|
|
7471
7838
|
}
|
|
7472
7839
|
return 'Run "bragduck auth login" to login again';
|
|
7473
7840
|
}
|
|
7841
|
+
if (error.name === "IntegrationError" && error.details?.hint) {
|
|
7842
|
+
return error.details.hint;
|
|
7843
|
+
}
|
|
7474
7844
|
if (error.name === "GoogleDocsError") {
|
|
7475
7845
|
return 'Run "bragduck auth google" to re-authenticate with Google';
|
|
7476
7846
|
}
|
|
@@ -7680,13 +8050,15 @@ function formatBragsTable(brags) {
|
|
|
7680
8050
|
return table.toString() + (links ? "\n\n" + links : "");
|
|
7681
8051
|
}
|
|
7682
8052
|
function formatBragLinks(brags) {
|
|
7683
|
-
const lines = brags.filter((brag) => brag.url || brag.
|
|
8053
|
+
const lines = brags.filter((brag) => brag.url || brag.keyResultIds && brag.keyResultIds.length > 0).map((brag) => {
|
|
7684
8054
|
const parts = [` ${colors.white(brag.title)}`];
|
|
7685
8055
|
if (brag.url) {
|
|
7686
8056
|
parts.push(` ${colors.info(brag.url)}`);
|
|
7687
8057
|
}
|
|
7688
|
-
if (brag.
|
|
7689
|
-
parts.push(
|
|
8058
|
+
if (brag.keyResultIds && brag.keyResultIds.length > 0) {
|
|
8059
|
+
parts.push(
|
|
8060
|
+
` ${colors.dim("Linked to key result:")} ${colors.primary(brag.keyResultIds[0])}`
|
|
8061
|
+
);
|
|
7690
8062
|
}
|
|
7691
8063
|
return parts.join("\n");
|
|
7692
8064
|
});
|
|
@@ -7698,9 +8070,9 @@ function formatBragsOneline(brags) {
|
|
|
7698
8070
|
const date = formatDate(brag.date);
|
|
7699
8071
|
const urlLine = brag.url ? `
|
|
7700
8072
|
${colors.info(brag.url)}` : "";
|
|
7701
|
-
const
|
|
7702
|
-
${colors.dim("Linked to key result:")} ${colors.primary(brag.
|
|
7703
|
-
return `${colors.highlight(date)} ${colors.white(brag.title)}${urlLine}${
|
|
8073
|
+
const keyResultLine = brag.keyResultIds && brag.keyResultIds.length > 0 ? `
|
|
8074
|
+
${colors.dim("Linked to key result:")} ${colors.primary(brag.keyResultIds[0])}` : "";
|
|
8075
|
+
return `${colors.highlight(date)} ${colors.white(brag.title)}${urlLine}${keyResultLine}`;
|
|
7704
8076
|
}).join("\n");
|
|
7705
8077
|
}
|
|
7706
8078
|
function truncateText(text, maxLength) {
|
|
@@ -7730,14 +8102,338 @@ function getErrorHint2(error) {
|
|
|
7730
8102
|
return "Try running with DEBUG=* for more information";
|
|
7731
8103
|
}
|
|
7732
8104
|
|
|
8105
|
+
// src/commands/story.ts
|
|
8106
|
+
init_esm_shims();
|
|
8107
|
+
init_api_service();
|
|
8108
|
+
init_auth_service();
|
|
8109
|
+
import boxen8 from "boxen";
|
|
8110
|
+
import Table3 from "cli-table3";
|
|
8111
|
+
init_logger();
|
|
8112
|
+
async function storyCommand(subcommand, idOrOptions, _options) {
|
|
8113
|
+
switch (subcommand) {
|
|
8114
|
+
case "list":
|
|
8115
|
+
case "ls":
|
|
8116
|
+
await storyListCommand(idOrOptions || {});
|
|
8117
|
+
break;
|
|
8118
|
+
case "create":
|
|
8119
|
+
case "add":
|
|
8120
|
+
await storyCreateCommand(idOrOptions || {});
|
|
8121
|
+
break;
|
|
8122
|
+
case "delete":
|
|
8123
|
+
case "rm":
|
|
8124
|
+
if (typeof idOrOptions === "string") {
|
|
8125
|
+
await storyDeleteCommand(idOrOptions);
|
|
8126
|
+
} else {
|
|
8127
|
+
logger.log("");
|
|
8128
|
+
logger.log(
|
|
8129
|
+
boxen8(
|
|
8130
|
+
formatErrorMessage("Missing story ID", "Usage: bragduck story delete <id>"),
|
|
8131
|
+
boxStyles.error
|
|
8132
|
+
)
|
|
8133
|
+
);
|
|
8134
|
+
process.exit(1);
|
|
8135
|
+
}
|
|
8136
|
+
break;
|
|
8137
|
+
default:
|
|
8138
|
+
logger.log("");
|
|
8139
|
+
logger.log(
|
|
8140
|
+
boxen8(
|
|
8141
|
+
colors.primary("Story Commands") + `
|
|
8142
|
+
|
|
8143
|
+
${theme.command("bragduck story list")} List your interview stories
|
|
8144
|
+
${theme.command("bragduck story create")} Create a new STAR story
|
|
8145
|
+
${theme.command("bragduck story delete")} Delete a story by ID
|
|
8146
|
+
|
|
8147
|
+
` + colors.dim("Aliases: ls, add, rm"),
|
|
8148
|
+
{ ...boxStyles.info, padding: 1 }
|
|
8149
|
+
)
|
|
8150
|
+
);
|
|
8151
|
+
break;
|
|
8152
|
+
}
|
|
8153
|
+
}
|
|
8154
|
+
async function storyListCommand(options) {
|
|
8155
|
+
logger.log("");
|
|
8156
|
+
try {
|
|
8157
|
+
const isAuthenticated = await ensureAuthenticated();
|
|
8158
|
+
if (!isAuthenticated) {
|
|
8159
|
+
process.exit(1);
|
|
8160
|
+
}
|
|
8161
|
+
const subSpinner = createValidateSpinner("Checking subscription...");
|
|
8162
|
+
subSpinner.start();
|
|
8163
|
+
let subscriptionStatus;
|
|
8164
|
+
try {
|
|
8165
|
+
subscriptionStatus = await apiService.getSubscriptionStatus();
|
|
8166
|
+
} catch (error) {
|
|
8167
|
+
failSpinner(subSpinner, "Failed to check subscription");
|
|
8168
|
+
throw error;
|
|
8169
|
+
}
|
|
8170
|
+
if (subscriptionStatus.tier === "FREE") {
|
|
8171
|
+
failSpinner(subSpinner, "Free plan \u2014 upgrade to use the CLI");
|
|
8172
|
+
showUpgradeMessage();
|
|
8173
|
+
return;
|
|
8174
|
+
}
|
|
8175
|
+
subSpinner.stop();
|
|
8176
|
+
const userInfo = authService.getUserInfo();
|
|
8177
|
+
if (!userInfo) {
|
|
8178
|
+
logger.log(boxen8(formatErrorMessage("Could not retrieve user info"), boxStyles.error));
|
|
8179
|
+
process.exit(1);
|
|
8180
|
+
}
|
|
8181
|
+
const perPage = options.limit || 20;
|
|
8182
|
+
const spinner = fetchingStoriesSpinner();
|
|
8183
|
+
spinner.start();
|
|
8184
|
+
const response = await apiService.listStories(userInfo.id, {
|
|
8185
|
+
perPage,
|
|
8186
|
+
search: options.search,
|
|
8187
|
+
competency: options.competency
|
|
8188
|
+
});
|
|
8189
|
+
if (response.items.length === 0) {
|
|
8190
|
+
failSpinner(spinner, "No stories found");
|
|
8191
|
+
logger.log("");
|
|
8192
|
+
if (options.search || options.competency) {
|
|
8193
|
+
logger.info("Try adjusting your filters or run without filters to see all stories");
|
|
8194
|
+
} else {
|
|
8195
|
+
logger.info(
|
|
8196
|
+
theme.secondary("Run ") + theme.command("bragduck story create") + theme.secondary(" to create your first interview story")
|
|
8197
|
+
);
|
|
8198
|
+
}
|
|
8199
|
+
return;
|
|
8200
|
+
}
|
|
8201
|
+
succeedSpinner(spinner, `Found ${response.total} stor${response.total > 1 ? "ies" : "y"}`);
|
|
8202
|
+
showCommandHeader(userInfo);
|
|
8203
|
+
logger.log("");
|
|
8204
|
+
logger.log(formatStoriesTable(response.items));
|
|
8205
|
+
logger.log("");
|
|
8206
|
+
if (response.total > response.items.length) {
|
|
8207
|
+
logger.info(`Showing ${response.items.length} of ${response.total} total stories`);
|
|
8208
|
+
logger.log("");
|
|
8209
|
+
}
|
|
8210
|
+
if (options.search || options.competency) {
|
|
8211
|
+
const filterInfo = [];
|
|
8212
|
+
if (options.search) filterInfo.push(`search: "${options.search}"`);
|
|
8213
|
+
if (options.competency) filterInfo.push(`competency: ${options.competency}`);
|
|
8214
|
+
logger.info(theme.info(`Filters applied: ${filterInfo.join(", ")}`));
|
|
8215
|
+
logger.log("");
|
|
8216
|
+
}
|
|
8217
|
+
} catch (error) {
|
|
8218
|
+
const err = error;
|
|
8219
|
+
logger.log("");
|
|
8220
|
+
logger.log(boxen8(formatErrorMessage(err.message, getErrorHint3(err)), boxStyles.error));
|
|
8221
|
+
process.exit(1);
|
|
8222
|
+
}
|
|
8223
|
+
}
|
|
8224
|
+
async function storyCreateCommand(options) {
|
|
8225
|
+
logger.log("");
|
|
8226
|
+
try {
|
|
8227
|
+
const isAuthenticated = await ensureAuthenticated();
|
|
8228
|
+
if (!isAuthenticated) {
|
|
8229
|
+
process.exit(1);
|
|
8230
|
+
}
|
|
8231
|
+
const subSpinner = createValidateSpinner("Checking subscription...");
|
|
8232
|
+
subSpinner.start();
|
|
8233
|
+
let subscriptionStatus;
|
|
8234
|
+
try {
|
|
8235
|
+
subscriptionStatus = await apiService.getSubscriptionStatus();
|
|
8236
|
+
} catch (error) {
|
|
8237
|
+
failSpinner(subSpinner, "Failed to check subscription");
|
|
8238
|
+
throw error;
|
|
8239
|
+
}
|
|
8240
|
+
if (subscriptionStatus.tier === "FREE") {
|
|
8241
|
+
failSpinner(subSpinner, "Free plan \u2014 upgrade to use the CLI");
|
|
8242
|
+
showUpgradeMessage();
|
|
8243
|
+
return;
|
|
8244
|
+
}
|
|
8245
|
+
subSpinner.stop();
|
|
8246
|
+
const userInfo = authService.getUserInfo();
|
|
8247
|
+
if (!userInfo) {
|
|
8248
|
+
logger.log(boxen8(formatErrorMessage("Could not retrieve user info"), boxStyles.error));
|
|
8249
|
+
process.exit(1);
|
|
8250
|
+
}
|
|
8251
|
+
logger.log(
|
|
8252
|
+
boxen8(
|
|
8253
|
+
colors.primary("Create Interview Story") + "\n\n" + colors.dim("Use the STAR format to structure your story:\n") + colors.dim("Situation, Task, Action, Result"),
|
|
8254
|
+
{ ...boxStyles.info, padding: 1 }
|
|
8255
|
+
)
|
|
8256
|
+
);
|
|
8257
|
+
logger.log("");
|
|
8258
|
+
const title = options.title || await promptInput("Story title (short summary):");
|
|
8259
|
+
if (!title.trim()) {
|
|
8260
|
+
logger.log(theme.error("Title is required"));
|
|
8261
|
+
process.exit(1);
|
|
8262
|
+
}
|
|
8263
|
+
const situation = await promptInput("Situation (context and background):");
|
|
8264
|
+
if (!situation.trim()) {
|
|
8265
|
+
logger.log(theme.error("Situation is required"));
|
|
8266
|
+
process.exit(1);
|
|
8267
|
+
}
|
|
8268
|
+
const task = await promptInput("Task (your responsibility or challenge):");
|
|
8269
|
+
if (!task.trim()) {
|
|
8270
|
+
logger.log(theme.error("Task is required"));
|
|
8271
|
+
process.exit(1);
|
|
8272
|
+
}
|
|
8273
|
+
const action = await promptInput("Action (what you did):");
|
|
8274
|
+
if (!action.trim()) {
|
|
8275
|
+
logger.log(theme.error("Action is required"));
|
|
8276
|
+
process.exit(1);
|
|
8277
|
+
}
|
|
8278
|
+
const result = await promptInput("Result (outcome and impact):");
|
|
8279
|
+
if (!result.trim()) {
|
|
8280
|
+
logger.log(theme.error("Result is required"));
|
|
8281
|
+
process.exit(1);
|
|
8282
|
+
}
|
|
8283
|
+
const competenciesInput = await promptInput(
|
|
8284
|
+
"Competencies (comma-separated, e.g. leadership, system design):",
|
|
8285
|
+
""
|
|
8286
|
+
);
|
|
8287
|
+
const competencies = competenciesInput ? competenciesInput.split(",").map((c) => c.trim()).filter(Boolean) : [];
|
|
8288
|
+
let orgId;
|
|
8289
|
+
try {
|
|
8290
|
+
const orgsResponse = await apiService.listUserOrganisations(userInfo.id);
|
|
8291
|
+
if (orgsResponse.items.length > 0) {
|
|
8292
|
+
const selectedOrg = await promptSelectOrganisationWithDefault(orgsResponse.items);
|
|
8293
|
+
if (selectedOrg) {
|
|
8294
|
+
orgId = selectedOrg;
|
|
8295
|
+
}
|
|
8296
|
+
}
|
|
8297
|
+
} catch {
|
|
8298
|
+
logger.debug("Failed to fetch organisations, skipping org selection");
|
|
8299
|
+
}
|
|
8300
|
+
logger.log("");
|
|
8301
|
+
logger.log(
|
|
8302
|
+
boxen8(
|
|
8303
|
+
colors.primary("Preview") + `
|
|
8304
|
+
|
|
8305
|
+
${theme.label("Title")} ${colors.white(title)}
|
|
8306
|
+
|
|
8307
|
+
${theme.label("Situation")} ${colors.white(situation)}
|
|
8308
|
+
|
|
8309
|
+
${theme.label("Task")} ${colors.white(task)}
|
|
8310
|
+
|
|
8311
|
+
${theme.label("Action")} ${colors.white(action)}
|
|
8312
|
+
|
|
8313
|
+
${theme.label("Result")} ${colors.white(result)}
|
|
8314
|
+
|
|
8315
|
+
${theme.label("Competencies")} ${competencies.length > 0 ? colors.primary(competencies.join(", ")) : colors.dim("none")}`,
|
|
8316
|
+
{ ...boxStyles.info, padding: 1 }
|
|
8317
|
+
)
|
|
8318
|
+
);
|
|
8319
|
+
logger.log("");
|
|
8320
|
+
const shouldCreate = await promptConfirm("Create this story?", true);
|
|
8321
|
+
if (!shouldCreate) {
|
|
8322
|
+
logger.log(theme.secondary("Story creation cancelled"));
|
|
8323
|
+
return;
|
|
8324
|
+
}
|
|
8325
|
+
const spinner = creatingStorySpinner();
|
|
8326
|
+
spinner.start();
|
|
8327
|
+
const body = {
|
|
8328
|
+
title,
|
|
8329
|
+
situation,
|
|
8330
|
+
task,
|
|
8331
|
+
action,
|
|
8332
|
+
result,
|
|
8333
|
+
competencies,
|
|
8334
|
+
source: "cli"
|
|
8335
|
+
};
|
|
8336
|
+
if (orgId) {
|
|
8337
|
+
body.orgId = orgId;
|
|
8338
|
+
}
|
|
8339
|
+
const story = await apiService.createStory(userInfo.id, body);
|
|
8340
|
+
succeedSpinner(spinner, `Story created: "${story.title}"`);
|
|
8341
|
+
logger.log("");
|
|
8342
|
+
} catch (error) {
|
|
8343
|
+
const err = error;
|
|
8344
|
+
logger.log("");
|
|
8345
|
+
logger.log(boxen8(formatErrorMessage(err.message, getErrorHint3(err)), boxStyles.error));
|
|
8346
|
+
process.exit(1);
|
|
8347
|
+
}
|
|
8348
|
+
}
|
|
8349
|
+
async function storyDeleteCommand(storyId) {
|
|
8350
|
+
logger.log("");
|
|
8351
|
+
try {
|
|
8352
|
+
const isAuthenticated = await ensureAuthenticated();
|
|
8353
|
+
if (!isAuthenticated) {
|
|
8354
|
+
process.exit(1);
|
|
8355
|
+
}
|
|
8356
|
+
const userInfo = authService.getUserInfo();
|
|
8357
|
+
if (!userInfo) {
|
|
8358
|
+
logger.log(boxen8(formatErrorMessage("Could not retrieve user info"), boxStyles.error));
|
|
8359
|
+
process.exit(1);
|
|
8360
|
+
}
|
|
8361
|
+
const shouldDelete = await promptConfirm(`Delete story ${storyId}?`, false);
|
|
8362
|
+
if (!shouldDelete) {
|
|
8363
|
+
logger.log(theme.secondary("Deletion cancelled"));
|
|
8364
|
+
return;
|
|
8365
|
+
}
|
|
8366
|
+
const spinner = deletingStorySpinner();
|
|
8367
|
+
spinner.start();
|
|
8368
|
+
await apiService.deleteStory(userInfo.id, storyId);
|
|
8369
|
+
succeedSpinner(spinner, "Story deleted");
|
|
8370
|
+
logger.log("");
|
|
8371
|
+
} catch (error) {
|
|
8372
|
+
const err = error;
|
|
8373
|
+
logger.log("");
|
|
8374
|
+
logger.log(boxen8(formatErrorMessage(err.message, getErrorHint3(err)), boxStyles.error));
|
|
8375
|
+
process.exit(1);
|
|
8376
|
+
}
|
|
8377
|
+
}
|
|
8378
|
+
function formatStoriesTable(stories) {
|
|
8379
|
+
const table = new Table3({
|
|
8380
|
+
head: [
|
|
8381
|
+
colors.primary("ID"),
|
|
8382
|
+
colors.primary("Title"),
|
|
8383
|
+
colors.primary("Competencies"),
|
|
8384
|
+
colors.primary("Updated")
|
|
8385
|
+
],
|
|
8386
|
+
colWidths: [12, 35, 30, 14],
|
|
8387
|
+
wordWrap: true,
|
|
8388
|
+
style: tableStyles.default.style
|
|
8389
|
+
});
|
|
8390
|
+
stories.forEach((story) => {
|
|
8391
|
+
const competencies = story.competencies.length > 0 ? story.competencies.join(", ") : colors.dim("none");
|
|
8392
|
+
const updated = formatDate(story.updatedAt);
|
|
8393
|
+
table.push([
|
|
8394
|
+
colors.dim(story.id),
|
|
8395
|
+
colors.white(story.title),
|
|
8396
|
+
colors.primary(competencies),
|
|
8397
|
+
colors.highlight(updated)
|
|
8398
|
+
]);
|
|
8399
|
+
});
|
|
8400
|
+
return table.toString();
|
|
8401
|
+
}
|
|
8402
|
+
function showUpgradeMessage() {
|
|
8403
|
+
logger.log("");
|
|
8404
|
+
logger.log(
|
|
8405
|
+
boxen8(
|
|
8406
|
+
theme.warning("CLI Access Requires Subscription") + "\n\nThe BragDuck CLI is available for Plus and Pro subscribers.\nUpgrade now to unlock:\n\n \u2022 List and manage your interview stories\n \u2022 Create STAR-format stories from the terminal\n \u2022 Automatic work item scanning\n\n" + colors.highlight("Upgrade at: https://bragduck.com/app/settings/plans"),
|
|
8407
|
+
{
|
|
8408
|
+
...boxStyles.warning,
|
|
8409
|
+
padding: 1,
|
|
8410
|
+
margin: 1
|
|
8411
|
+
}
|
|
8412
|
+
)
|
|
8413
|
+
);
|
|
8414
|
+
logger.log("");
|
|
8415
|
+
}
|
|
8416
|
+
function getErrorHint3(error) {
|
|
8417
|
+
if (error.name === "TokenExpiredError" || error.name === "AuthenticationError") {
|
|
8418
|
+
return 'Run "bragduck auth login" to login again';
|
|
8419
|
+
}
|
|
8420
|
+
if (error.name === "NetworkError") {
|
|
8421
|
+
return "Check your internet connection and try again";
|
|
8422
|
+
}
|
|
8423
|
+
if (error.name === "ApiError") {
|
|
8424
|
+
return "The server might be experiencing issues. Try again later";
|
|
8425
|
+
}
|
|
8426
|
+
return "Try running with DEBUG=* for more information";
|
|
8427
|
+
}
|
|
8428
|
+
|
|
7733
8429
|
// src/commands/config.ts
|
|
7734
8430
|
init_esm_shims();
|
|
7735
8431
|
init_storage_service();
|
|
7736
8432
|
init_logger();
|
|
7737
8433
|
init_constants();
|
|
7738
|
-
import
|
|
8434
|
+
import boxen9 from "boxen";
|
|
7739
8435
|
import chalk6 from "chalk";
|
|
7740
|
-
import
|
|
8436
|
+
import Table4 from "cli-table3";
|
|
7741
8437
|
init_errors();
|
|
7742
8438
|
var VALID_CONFIG_KEYS = Object.values(CONFIG_KEYS);
|
|
7743
8439
|
async function configCommand(subcommand, key, value) {
|
|
@@ -7771,7 +8467,7 @@ async function configCommand(subcommand, key, value) {
|
|
|
7771
8467
|
const err = error;
|
|
7772
8468
|
logger.log("");
|
|
7773
8469
|
logger.log(
|
|
7774
|
-
|
|
8470
|
+
boxen9(formatErrorMessage(err.message, getConfigHint(err)), {
|
|
7775
8471
|
padding: 1,
|
|
7776
8472
|
margin: 1,
|
|
7777
8473
|
borderStyle: "round",
|
|
@@ -7791,7 +8487,7 @@ async function handleListConfig() {
|
|
|
7791
8487
|
confluenceInstance: storageService.getConfig("confluenceInstance"),
|
|
7792
8488
|
gitlabInstance: storageService.getConfig("gitlabInstance")
|
|
7793
8489
|
};
|
|
7794
|
-
const table = new
|
|
8490
|
+
const table = new Table4({
|
|
7795
8491
|
head: [chalk6.cyan("Key"), chalk6.cyan("Value"), chalk6.cyan("Default")],
|
|
7796
8492
|
colWidths: [25, 40, 40],
|
|
7797
8493
|
wordWrap: true,
|
|
@@ -7865,7 +8561,7 @@ async function handleSetConfig(key, value) {
|
|
|
7865
8561
|
const typedValue = validateAndConvertValue(key, value);
|
|
7866
8562
|
storageService.setConfig(key, typedValue);
|
|
7867
8563
|
logger.log(
|
|
7868
|
-
|
|
8564
|
+
boxen9(
|
|
7869
8565
|
`${chalk6.green.bold("\u2713 Configuration updated")}
|
|
7870
8566
|
|
|
7871
8567
|
${chalk6.white(key)}: ${chalk6.yellow(String(typedValue))}`,
|
|
@@ -7997,6 +8693,18 @@ program.command("list").description("List your existing brags").option("-l, --li
|
|
|
7997
8693
|
process.exit(1);
|
|
7998
8694
|
}
|
|
7999
8695
|
});
|
|
8696
|
+
program.command("story [subcommand] [id]").description("Manage interview stories (subcommands: list, create, delete)").option("-l, --limit <number>", "Number of stories to display", (val) => parseInt(val, 10), 20).option("-s, --search <query>", "Search stories by keyword").option("-c, --competency <competency>", "Filter by competency").option("-t, --title <title>", "Story title (for create)").action(async (subcommand, id, options) => {
|
|
8697
|
+
try {
|
|
8698
|
+
if (subcommand === "delete" || subcommand === "rm") {
|
|
8699
|
+
await storyCommand(subcommand, id, options);
|
|
8700
|
+
} else {
|
|
8701
|
+
await storyCommand(subcommand, options);
|
|
8702
|
+
}
|
|
8703
|
+
} catch (error) {
|
|
8704
|
+
console.error(error);
|
|
8705
|
+
process.exit(1);
|
|
8706
|
+
}
|
|
8707
|
+
});
|
|
8000
8708
|
program.command("logout").description("Clear stored credentials").action(async () => {
|
|
8001
8709
|
try {
|
|
8002
8710
|
await logoutCommand();
|