@bragduck/mcp 1.0.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/README.md +114 -0
- package/dist/api-client.d.ts +9 -0
- package/dist/api-client.js +102 -0
- package/dist/api-client.js.map +1 -0
- package/dist/auth.d.ts +8 -0
- package/dist/auth.js +202 -0
- package/dist/auth.js.map +1 -0
- package/dist/credentials.d.ts +31 -0
- package/dist/credentials.js +150 -0
- package/dist/credentials.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/link-brag-to-goal.d.ts +30 -0
- package/dist/tools/link-brag-to-goal.js +64 -0
- package/dist/tools/link-brag-to-goal.js.map +1 -0
- package/dist/tools/list-brags.d.ts +37 -0
- package/dist/tools/list-brags.js +71 -0
- package/dist/tools/list-brags.js.map +1 -0
- package/dist/tools/list-objectives.d.ts +15 -0
- package/dist/tools/list-objectives.js +63 -0
- package/dist/tools/list-objectives.js.map +1 -0
- package/dist/tools/log-brag.d.ts +69 -0
- package/dist/tools/log-brag.js +131 -0
- package/dist/tools/log-brag.js.map +1 -0
- package/dist/tools/login.d.ts +24 -0
- package/dist/tools/login.js +46 -0
- package/dist/tools/login.js.map +1 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# @bragduck/mcp
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server that lets AI agents log career achievements to [BragDuck](https://bragduck.com).
|
|
4
|
+
|
|
5
|
+
Works with any MCP-compatible client: Claude Code, Cursor, Windsurf, and others.
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
### Claude Code
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
claude mcp add bragduck -- npx @bragduck/mcp
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Cursor
|
|
16
|
+
|
|
17
|
+
Add to `~/.cursor/mcp.json`:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"mcpServers": {
|
|
22
|
+
"bragduck": {
|
|
23
|
+
"command": "npx",
|
|
24
|
+
"args": ["@bragduck/mcp"]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### VS Code (Copilot)
|
|
31
|
+
|
|
32
|
+
Add to your VS Code settings JSON:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"mcp": {
|
|
37
|
+
"servers": {
|
|
38
|
+
"bragduck": {
|
|
39
|
+
"command": "npx",
|
|
40
|
+
"args": ["@bragduck/mcp"]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Authentication
|
|
48
|
+
|
|
49
|
+
On first use, call the `login` tool. This opens your browser for OAuth authentication with BragDuck. Credentials are stored locally in `~/.bragduck/credentials.enc` (encrypted, shared with the BragDuck CLI).
|
|
50
|
+
|
|
51
|
+
## Tools
|
|
52
|
+
|
|
53
|
+
### `login`
|
|
54
|
+
|
|
55
|
+
Authenticate with BragDuck via browser-based OAuth.
|
|
56
|
+
|
|
57
|
+
| Parameter | Type | Required | Description |
|
|
58
|
+
|-----------|------|----------|-------------|
|
|
59
|
+
| `force` | boolean | No | Force re-authentication even if already logged in |
|
|
60
|
+
|
|
61
|
+
### `log_brag`
|
|
62
|
+
|
|
63
|
+
Log a career achievement to BragDuck.
|
|
64
|
+
|
|
65
|
+
| Parameter | Type | Required | Description |
|
|
66
|
+
|-----------|------|----------|-------------|
|
|
67
|
+
| `title` | string | Yes | Short title (10-200 characters) |
|
|
68
|
+
| `description` | string | Yes | Detailed description of what was accomplished (50-2000 characters) |
|
|
69
|
+
| `type` | string | Yes | Category: `progress`, `achievement`, `recognition`, `support`, `code`, `leadership`, `learning`, `collaboration`, `challenge`, `innovation`, `meeting`, `bug`, `others` |
|
|
70
|
+
| `impact_level` | number | Yes | 0 = none, 1 = low (individual), 2 = medium (team), 3 = high (organization) |
|
|
71
|
+
| `impact_description` | string | Yes | Narrative with scope and measurable outcomes |
|
|
72
|
+
| `tags` | string[] | No | Tags to categorize the brag |
|
|
73
|
+
| `date` | string | No | ISO datetime (defaults to now) |
|
|
74
|
+
| `orgId` | string | No | Organisation ID to associate with |
|
|
75
|
+
|
|
76
|
+
### `list_brags`
|
|
77
|
+
|
|
78
|
+
List recent brags with optional filtering.
|
|
79
|
+
|
|
80
|
+
| Parameter | Type | Required | Description |
|
|
81
|
+
|-----------|------|----------|-------------|
|
|
82
|
+
| `limit` | number | No | Number of brags to return (default: 10, max: 50) |
|
|
83
|
+
| `search` | string | No | Search by title or description |
|
|
84
|
+
| `tags` | string[] | No | Filter by tags |
|
|
85
|
+
|
|
86
|
+
### `list_objectives`
|
|
87
|
+
|
|
88
|
+
List your OKRs (Objectives and Key Results). Returns goal IDs that can be used with `link_brag_to_goal`.
|
|
89
|
+
|
|
90
|
+
No parameters required.
|
|
91
|
+
|
|
92
|
+
### `link_brag_to_goal`
|
|
93
|
+
|
|
94
|
+
Link a brag to a goal/key-result.
|
|
95
|
+
|
|
96
|
+
| Parameter | Type | Required | Description |
|
|
97
|
+
|-----------|------|----------|-------------|
|
|
98
|
+
| `bragId` | string | Yes | The brag ID |
|
|
99
|
+
| `goalId` | string | Yes | The goal ID |
|
|
100
|
+
|
|
101
|
+
## Environment Variables
|
|
102
|
+
|
|
103
|
+
| Variable | Default | Description |
|
|
104
|
+
|----------|---------|-------------|
|
|
105
|
+
| `BRAGDUCK_API_URL` | `https://api.bragduck.com` | Override API base URL |
|
|
106
|
+
|
|
107
|
+
## Requirements
|
|
108
|
+
|
|
109
|
+
- Node.js >= 20.0.0
|
|
110
|
+
- A [BragDuck](https://bragduck.com) account
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
|
|
114
|
+
MIT
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Make an authenticated API request to the BragDuck backend.
|
|
3
|
+
* Automatically retries once on 401 with a refreshed token.
|
|
4
|
+
*/
|
|
5
|
+
export declare function apiRequest<T>(endpoint: string, options?: {
|
|
6
|
+
method?: string;
|
|
7
|
+
body?: unknown;
|
|
8
|
+
params?: Record<string, string | string[]>;
|
|
9
|
+
}): Promise<T>;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { getAccessToken, getRefreshToken, storeCredentials } from './credentials.js';
|
|
2
|
+
const API_BASE_URL = process.env.BRAGDUCK_API_URL || 'https://api.bragduck.com';
|
|
3
|
+
const OAUTH_CLIENT_ID = 'bragduck-cli';
|
|
4
|
+
const TOKEN_ENDPOINT = '/v1/auth/cli/token';
|
|
5
|
+
/**
|
|
6
|
+
* Attempt to refresh the access token using the stored refresh token
|
|
7
|
+
*/
|
|
8
|
+
async function refreshAccessToken() {
|
|
9
|
+
const refreshToken = getRefreshToken();
|
|
10
|
+
if (!refreshToken)
|
|
11
|
+
return null;
|
|
12
|
+
try {
|
|
13
|
+
const response = await fetch(`${API_BASE_URL}${TOKEN_ENDPOINT}`, {
|
|
14
|
+
method: 'POST',
|
|
15
|
+
headers: { 'Content-Type': 'application/json' },
|
|
16
|
+
body: JSON.stringify({
|
|
17
|
+
refresh_token: refreshToken,
|
|
18
|
+
client_id: OAUTH_CLIENT_ID,
|
|
19
|
+
grant_type: 'refresh_token',
|
|
20
|
+
}),
|
|
21
|
+
});
|
|
22
|
+
if (!response.ok)
|
|
23
|
+
return null;
|
|
24
|
+
const data = await response.json();
|
|
25
|
+
const expiresAt = data.expires_in
|
|
26
|
+
? Date.now() + data.expires_in * 1000
|
|
27
|
+
: undefined;
|
|
28
|
+
storeCredentials({
|
|
29
|
+
accessToken: data.access_token,
|
|
30
|
+
refreshToken: data.refresh_token || refreshToken,
|
|
31
|
+
expiresAt,
|
|
32
|
+
});
|
|
33
|
+
return data.access_token;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Make an authenticated API request to the BragDuck backend.
|
|
41
|
+
* Automatically retries once on 401 with a refreshed token.
|
|
42
|
+
*/
|
|
43
|
+
export async function apiRequest(endpoint, options = {}) {
|
|
44
|
+
const { method = 'GET', body, params } = options;
|
|
45
|
+
let token = getAccessToken();
|
|
46
|
+
if (!token) {
|
|
47
|
+
throw new Error('NOT_AUTHENTICATED');
|
|
48
|
+
}
|
|
49
|
+
let url = `${API_BASE_URL}${endpoint}`;
|
|
50
|
+
if (params) {
|
|
51
|
+
const searchParams = new URLSearchParams();
|
|
52
|
+
for (const [key, value] of Object.entries(params)) {
|
|
53
|
+
if (Array.isArray(value)) {
|
|
54
|
+
value.forEach((v) => searchParams.append(`${key}[]`, v));
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
searchParams.append(key, value);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
url += `?${searchParams.toString()}`;
|
|
61
|
+
}
|
|
62
|
+
const headers = {
|
|
63
|
+
Authorization: `Bearer ${token}`,
|
|
64
|
+
'User-Agent': 'BragDuck-MCP/1.0.0',
|
|
65
|
+
};
|
|
66
|
+
if (body) {
|
|
67
|
+
headers['Content-Type'] = 'application/json';
|
|
68
|
+
}
|
|
69
|
+
let response = await fetch(url, {
|
|
70
|
+
method,
|
|
71
|
+
headers,
|
|
72
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
73
|
+
});
|
|
74
|
+
// On 401, try refreshing the token once
|
|
75
|
+
if (response.status === 401) {
|
|
76
|
+
const newToken = await refreshAccessToken();
|
|
77
|
+
if (!newToken) {
|
|
78
|
+
throw new Error('TOKEN_EXPIRED');
|
|
79
|
+
}
|
|
80
|
+
headers.Authorization = `Bearer ${newToken}`;
|
|
81
|
+
response = await fetch(url, {
|
|
82
|
+
method,
|
|
83
|
+
headers,
|
|
84
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
const text = await response.text().catch(() => '');
|
|
89
|
+
let msg = `API error: ${response.status} ${response.statusText}`;
|
|
90
|
+
try {
|
|
91
|
+
const errorData = JSON.parse(text);
|
|
92
|
+
msg = errorData.message || errorData.error || msg;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
if (text)
|
|
96
|
+
msg = text;
|
|
97
|
+
}
|
|
98
|
+
throw new Error(msg);
|
|
99
|
+
}
|
|
100
|
+
return response.json();
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=api-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"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"}
|
package/dist/auth.d.ts
ADDED
package/dist/auth.js
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { randomBytes } from 'crypto';
|
|
2
|
+
import { createServer } from 'http';
|
|
3
|
+
import { parse } from 'url';
|
|
4
|
+
import { exec } from 'child_process';
|
|
5
|
+
import { promisify } from 'util';
|
|
6
|
+
import { storeCredentials, storeUserInfo } from './credentials.js';
|
|
7
|
+
const execAsync = promisify(exec);
|
|
8
|
+
const API_BASE_URL = process.env.BRAGDUCK_API_URL || 'https://api.bragduck.com';
|
|
9
|
+
const OAUTH_CLIENT_ID = 'bragduck-cli';
|
|
10
|
+
const CALLBACK_PATH = '/callback';
|
|
11
|
+
const TIMEOUT_MS = 120000;
|
|
12
|
+
const MIN_PORT = 8010;
|
|
13
|
+
const MAX_PORT = 8014;
|
|
14
|
+
async function findAvailablePort() {
|
|
15
|
+
for (let port = MIN_PORT; port <= MAX_PORT; port++) {
|
|
16
|
+
try {
|
|
17
|
+
await new Promise((resolve, reject) => {
|
|
18
|
+
const testServer = createServer();
|
|
19
|
+
testServer.once('error', reject);
|
|
20
|
+
testServer.once('listening', () => {
|
|
21
|
+
testServer.close(() => resolve());
|
|
22
|
+
});
|
|
23
|
+
testServer.listen(port, '127.0.0.1');
|
|
24
|
+
});
|
|
25
|
+
return port;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
throw new Error(`No available ports found in range ${MIN_PORT}-${MAX_PORT}`);
|
|
32
|
+
}
|
|
33
|
+
async function openBrowser(url) {
|
|
34
|
+
const platform = process.platform;
|
|
35
|
+
let command;
|
|
36
|
+
switch (platform) {
|
|
37
|
+
case 'darwin':
|
|
38
|
+
command = `open "${url}"`;
|
|
39
|
+
break;
|
|
40
|
+
case 'win32':
|
|
41
|
+
command = `start "" "${url}"`;
|
|
42
|
+
break;
|
|
43
|
+
default:
|
|
44
|
+
command = `xdg-open "${url}"`;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
await execAsync(command);
|
|
48
|
+
}
|
|
49
|
+
const SUCCESS_HTML = `<!DOCTYPE html>
|
|
50
|
+
<html><head><meta charset="UTF-8"><title>BragDuck - Authenticated</title>
|
|
51
|
+
<style>
|
|
52
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
53
|
+
display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0;
|
|
54
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; }
|
|
55
|
+
.container { text-align: center; padding: 2rem; background: rgba(255,255,255,0.1);
|
|
56
|
+
border-radius: 1rem; backdrop-filter: blur(10px); max-width: 500px; }
|
|
57
|
+
h1 { font-size: 2rem; margin: 0 0 1rem; }
|
|
58
|
+
.check { font-size: 4rem; animation: pop 0.3s ease-out; }
|
|
59
|
+
p { font-size: 1.1rem; opacity: 0.9; }
|
|
60
|
+
@keyframes pop { from { transform: scale(0); } to { transform: scale(1); } }
|
|
61
|
+
</style></head><body>
|
|
62
|
+
<div class="container">
|
|
63
|
+
<div class="check">✓</div>
|
|
64
|
+
<h1>Authentication Successful</h1>
|
|
65
|
+
<p>You can close this window and return to your editor.</p>
|
|
66
|
+
</div></body></html>`;
|
|
67
|
+
const ERROR_HTML = (error) => `<!DOCTYPE html>
|
|
68
|
+
<html><head><meta charset="UTF-8"><title>BragDuck - Auth Failed</title>
|
|
69
|
+
<style>
|
|
70
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
71
|
+
display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0;
|
|
72
|
+
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); color: white; }
|
|
73
|
+
.container { text-align: center; padding: 2rem; background: rgba(255,255,255,0.1);
|
|
74
|
+
border-radius: 1rem; backdrop-filter: blur(10px); max-width: 500px; }
|
|
75
|
+
h1 { font-size: 2rem; margin: 0 0 1rem; }
|
|
76
|
+
.err { font-size: 4rem; }
|
|
77
|
+
p { font-size: 1.1rem; opacity: 0.9; }
|
|
78
|
+
.details { background: rgba(0,0,0,0.2); padding: 1rem; border-radius: 0.5rem;
|
|
79
|
+
font-family: monospace; font-size: 0.9rem; margin-top: 1rem; }
|
|
80
|
+
</style></head><body>
|
|
81
|
+
<div class="container">
|
|
82
|
+
<div class="err">✗</div>
|
|
83
|
+
<h1>Authentication Failed</h1>
|
|
84
|
+
<p>Please try again.</p>
|
|
85
|
+
<div class="details">${error}</div>
|
|
86
|
+
</div></body></html>`;
|
|
87
|
+
async function exchangeCodeForToken(code, callbackUrl) {
|
|
88
|
+
const response = await fetch(`${API_BASE_URL}/v1/auth/cli/token`, {
|
|
89
|
+
method: 'POST',
|
|
90
|
+
headers: { 'Content-Type': 'application/json' },
|
|
91
|
+
body: JSON.stringify({
|
|
92
|
+
code,
|
|
93
|
+
redirect_uri: callbackUrl,
|
|
94
|
+
client_id: OAUTH_CLIENT_ID,
|
|
95
|
+
grant_type: 'authorization_code',
|
|
96
|
+
}),
|
|
97
|
+
});
|
|
98
|
+
if (!response.ok) {
|
|
99
|
+
const errorData = await response.json().catch(() => ({}));
|
|
100
|
+
throw new Error(errorData.message || `Token exchange failed: ${response.statusText}`);
|
|
101
|
+
}
|
|
102
|
+
return response.json();
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Run the full OAuth login flow: start callback server, open browser, wait for auth.
|
|
106
|
+
* Returns user info on success.
|
|
107
|
+
*/
|
|
108
|
+
export async function login() {
|
|
109
|
+
const state = randomBytes(32).toString('hex');
|
|
110
|
+
const port = await findAvailablePort();
|
|
111
|
+
const callbackUrl = `http://127.0.0.1:${port}${CALLBACK_PATH}`;
|
|
112
|
+
const authParams = new URLSearchParams({
|
|
113
|
+
client_id: OAUTH_CLIENT_ID,
|
|
114
|
+
redirect_uri: callbackUrl,
|
|
115
|
+
state,
|
|
116
|
+
});
|
|
117
|
+
const authUrl = `${API_BASE_URL}/v1/auth/cli/initiate?${authParams.toString()}`;
|
|
118
|
+
return new Promise((resolve, reject) => {
|
|
119
|
+
let server = null;
|
|
120
|
+
let timeoutId = null;
|
|
121
|
+
const cleanup = () => {
|
|
122
|
+
if (timeoutId)
|
|
123
|
+
globalThis.clearTimeout(timeoutId);
|
|
124
|
+
if (server) {
|
|
125
|
+
if (typeof server.closeAllConnections === 'function') {
|
|
126
|
+
server.closeAllConnections();
|
|
127
|
+
}
|
|
128
|
+
server.close();
|
|
129
|
+
server.unref();
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
const handleRequest = async (req, res) => {
|
|
133
|
+
const parsedUrl = parse(req.url || '', true);
|
|
134
|
+
if (parsedUrl.pathname !== CALLBACK_PATH) {
|
|
135
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
136
|
+
res.end('Not Found');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const { code, state: returnedState, error, error_description } = parsedUrl.query;
|
|
140
|
+
if (error) {
|
|
141
|
+
const errorMsg = String(error_description || error);
|
|
142
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
143
|
+
res.end(ERROR_HTML(errorMsg));
|
|
144
|
+
cleanup();
|
|
145
|
+
reject(new Error(`OAuth error: ${errorMsg}`));
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (!code || !returnedState) {
|
|
149
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
150
|
+
res.end(ERROR_HTML('Missing code or state parameter'));
|
|
151
|
+
cleanup();
|
|
152
|
+
reject(new Error('Missing code or state parameter'));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (returnedState !== state) {
|
|
156
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
157
|
+
res.end(ERROR_HTML('Invalid state parameter'));
|
|
158
|
+
cleanup();
|
|
159
|
+
reject(new Error('Invalid state parameter (possible CSRF)'));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
163
|
+
res.end(SUCCESS_HTML);
|
|
164
|
+
try {
|
|
165
|
+
const tokenResponse = await exchangeCodeForToken(String(code), callbackUrl);
|
|
166
|
+
const expiresAt = tokenResponse.expires_in
|
|
167
|
+
? Date.now() + tokenResponse.expires_in * 1000
|
|
168
|
+
: undefined;
|
|
169
|
+
storeCredentials({
|
|
170
|
+
accessToken: tokenResponse.access_token,
|
|
171
|
+
refreshToken: tokenResponse.refresh_token,
|
|
172
|
+
expiresAt,
|
|
173
|
+
});
|
|
174
|
+
const user = tokenResponse.user || { id: 'unknown', email: 'unknown', name: 'Unknown' };
|
|
175
|
+
storeUserInfo(user);
|
|
176
|
+
globalThis.setTimeout(() => {
|
|
177
|
+
cleanup();
|
|
178
|
+
resolve({ email: user.email, name: user.name });
|
|
179
|
+
}, 100);
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
cleanup();
|
|
183
|
+
reject(err);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
server = createServer(handleRequest);
|
|
187
|
+
server.on('error', (err) => {
|
|
188
|
+
cleanup();
|
|
189
|
+
reject(new Error(`OAuth server error: ${err.message}`));
|
|
190
|
+
});
|
|
191
|
+
server.listen(port, '127.0.0.1', () => {
|
|
192
|
+
openBrowser(authUrl).catch(() => {
|
|
193
|
+
// If browser opening fails, we'll report it but keep the server running
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
timeoutId = globalThis.setTimeout(() => {
|
|
197
|
+
cleanup();
|
|
198
|
+
reject(new Error('Authentication timed out. No callback received within 2 minutes.'));
|
|
199
|
+
}, TIMEOUT_MS);
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
interface ServiceCredentials {
|
|
2
|
+
accessToken: string;
|
|
3
|
+
refreshToken?: string;
|
|
4
|
+
expiresAt?: number;
|
|
5
|
+
}
|
|
6
|
+
interface UserInfo {
|
|
7
|
+
id: string;
|
|
8
|
+
email: string;
|
|
9
|
+
name: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Get the bragduck access token from stored credentials
|
|
13
|
+
*/
|
|
14
|
+
export declare function getAccessToken(): string | null;
|
|
15
|
+
/**
|
|
16
|
+
* Get the refresh token from stored credentials
|
|
17
|
+
*/
|
|
18
|
+
export declare function getRefreshToken(): string | null;
|
|
19
|
+
/**
|
|
20
|
+
* Store credentials after login or token refresh
|
|
21
|
+
*/
|
|
22
|
+
export declare function storeCredentials(creds: ServiceCredentials): void;
|
|
23
|
+
/**
|
|
24
|
+
* Get stored user info from the Conf config store (shared with CLI)
|
|
25
|
+
*/
|
|
26
|
+
export declare function getUserInfo(): UserInfo | null;
|
|
27
|
+
/**
|
|
28
|
+
* Store user info in the Conf config store (shared with CLI)
|
|
29
|
+
*/
|
|
30
|
+
export declare function storeUserInfo(userInfo: UserInfo): void;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { createDecipheriv, scryptSync, randomBytes, createCipheriv } from 'crypto';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import Conf from 'conf';
|
|
6
|
+
const APP_NAME = 'bragduck';
|
|
7
|
+
const CREDENTIALS_DIR = '.bragduck';
|
|
8
|
+
const CREDENTIALS_FILE = 'credentials.enc';
|
|
9
|
+
const ENCRYPTION = {
|
|
10
|
+
ALGORITHM: 'aes-256-gcm',
|
|
11
|
+
KEY_LENGTH: 32,
|
|
12
|
+
IV_LENGTH: 16,
|
|
13
|
+
AUTH_TAG_LENGTH: 16,
|
|
14
|
+
SALT_LENGTH: 32,
|
|
15
|
+
};
|
|
16
|
+
function deriveEncryptionKey(salt) {
|
|
17
|
+
const password = `${APP_NAME}-${homedir()}-${process.platform}`;
|
|
18
|
+
return scryptSync(password, salt, ENCRYPTION.KEY_LENGTH);
|
|
19
|
+
}
|
|
20
|
+
function decrypt(encryptedData, key) {
|
|
21
|
+
const decipher = createDecipheriv(ENCRYPTION.ALGORITHM, key, Buffer.from(encryptedData.iv, 'hex'), { authTagLength: ENCRYPTION.AUTH_TAG_LENGTH });
|
|
22
|
+
decipher.setAuthTag(Buffer.from(encryptedData.authTag, 'hex'));
|
|
23
|
+
let decrypted = decipher.update(encryptedData.encrypted, 'hex', 'utf8');
|
|
24
|
+
decrypted += decipher.final('utf8');
|
|
25
|
+
return decrypted;
|
|
26
|
+
}
|
|
27
|
+
function encrypt(data, key) {
|
|
28
|
+
const iv = randomBytes(ENCRYPTION.IV_LENGTH);
|
|
29
|
+
const cipher = createCipheriv(ENCRYPTION.ALGORITHM, key, iv, {
|
|
30
|
+
authTagLength: ENCRYPTION.AUTH_TAG_LENGTH,
|
|
31
|
+
});
|
|
32
|
+
let encrypted = cipher.update(data, 'utf8', 'hex');
|
|
33
|
+
encrypted += cipher.final('hex');
|
|
34
|
+
const authTag = cipher.getAuthTag();
|
|
35
|
+
return {
|
|
36
|
+
encrypted,
|
|
37
|
+
iv: iv.toString('hex'),
|
|
38
|
+
authTag: authTag.toString('hex'),
|
|
39
|
+
salt: '',
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Read stored credentials from the CLI's encrypted file
|
|
44
|
+
*/
|
|
45
|
+
function readStoredCredentials() {
|
|
46
|
+
const credentialsPath = join(homedir(), CREDENTIALS_DIR, CREDENTIALS_FILE);
|
|
47
|
+
if (!existsSync(credentialsPath)) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const encryptedData = JSON.parse(readFileSync(credentialsPath, 'utf8'));
|
|
52
|
+
const salt = Buffer.from(encryptedData.salt, 'hex');
|
|
53
|
+
const key = deriveEncryptionKey(salt);
|
|
54
|
+
const decrypted = decrypt(encryptedData, key);
|
|
55
|
+
return JSON.parse(decrypted);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Write credentials to the encrypted file (shared with CLI)
|
|
63
|
+
*/
|
|
64
|
+
function writeStoredCredentials(credentials) {
|
|
65
|
+
const credentialsDir = join(homedir(), CREDENTIALS_DIR);
|
|
66
|
+
const credentialsPath = join(credentialsDir, CREDENTIALS_FILE);
|
|
67
|
+
if (!existsSync(credentialsDir)) {
|
|
68
|
+
mkdirSync(credentialsDir, { recursive: true, mode: 0o700 });
|
|
69
|
+
}
|
|
70
|
+
const data = JSON.stringify(credentials);
|
|
71
|
+
const salt = randomBytes(ENCRYPTION.SALT_LENGTH);
|
|
72
|
+
const key = deriveEncryptionKey(salt);
|
|
73
|
+
const encrypted = encrypt(data, key);
|
|
74
|
+
encrypted.salt = salt.toString('hex');
|
|
75
|
+
writeFileSync(credentialsPath, JSON.stringify(encrypted), {
|
|
76
|
+
mode: 0o600,
|
|
77
|
+
encoding: 'utf8',
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Get the bragduck access token from stored credentials
|
|
82
|
+
*/
|
|
83
|
+
export function getAccessToken() {
|
|
84
|
+
const credentials = readStoredCredentials();
|
|
85
|
+
if (!credentials)
|
|
86
|
+
return null;
|
|
87
|
+
// Handle legacy format
|
|
88
|
+
if (credentials.accessToken && !credentials.services) {
|
|
89
|
+
return credentials.accessToken;
|
|
90
|
+
}
|
|
91
|
+
return credentials.services?.bragduck?.accessToken || null;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Get the refresh token from stored credentials
|
|
95
|
+
*/
|
|
96
|
+
export function getRefreshToken() {
|
|
97
|
+
const credentials = readStoredCredentials();
|
|
98
|
+
if (!credentials)
|
|
99
|
+
return null;
|
|
100
|
+
if (credentials.accessToken && !credentials.services) {
|
|
101
|
+
return credentials.refreshToken || null;
|
|
102
|
+
}
|
|
103
|
+
return credentials.services?.bragduck?.refreshToken || null;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Store credentials after login or token refresh
|
|
107
|
+
*/
|
|
108
|
+
export function storeCredentials(creds) {
|
|
109
|
+
let existing = readStoredCredentials();
|
|
110
|
+
// Migrate legacy format
|
|
111
|
+
if (existing && existing.accessToken && !existing.services) {
|
|
112
|
+
existing = {
|
|
113
|
+
services: {
|
|
114
|
+
bragduck: {
|
|
115
|
+
accessToken: existing.accessToken,
|
|
116
|
+
refreshToken: existing.refreshToken,
|
|
117
|
+
expiresAt: existing.expiresAt,
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
const updated = {
|
|
123
|
+
...existing,
|
|
124
|
+
services: {
|
|
125
|
+
...existing?.services,
|
|
126
|
+
bragduck: creds,
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
writeStoredCredentials(updated);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Get stored user info from the Conf config store (shared with CLI)
|
|
133
|
+
*/
|
|
134
|
+
export function getUserInfo() {
|
|
135
|
+
try {
|
|
136
|
+
const config = new Conf({ projectName: APP_NAME });
|
|
137
|
+
return config.get('userInfo') || null;
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Store user info in the Conf config store (shared with CLI)
|
|
145
|
+
*/
|
|
146
|
+
export function storeUserInfo(userInfo) {
|
|
147
|
+
const config = new Conf({ projectName: APP_NAME });
|
|
148
|
+
config.set('userInfo', userInfo);
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED