borgmcp 0.2.0-beta.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/README.md ADDED
@@ -0,0 +1,250 @@
1
+ # Borg MCP Client
2
+
3
+ Local MCP client that syncs your `~/.claude/CLAUDE.md` file to the Borg MCP cloud service.
4
+
5
+ ## Features
6
+
7
+ - **Automatic Sync**: Watches `~/.claude/CLAUDE.md` and auto-syncs changes to cloud
8
+ - **Conflict Detection**: Never lose data - conflicts are detected and backed up for manual resolution
9
+ - **Secure Storage**: OAuth tokens stored in OS keychain (macOS Keychain, Windows Credential Manager, Linux Secret Service)
10
+ - **Offline Support**: Exponential backoff retry logic handles network failures gracefully
11
+ - **Google OAuth**: Secure authentication via Google OAuth 2.0 device code flow
12
+
13
+ ## Prerequisites
14
+
15
+ 1. **Node.js 18+** with npm
16
+ 2. **Google OAuth Credentials** (required for authentication)
17
+ - Client ID
18
+ - Client Secret
19
+ 3. **Borg MCP Subscription** ($2/month with 7-day trial)
20
+
21
+ ## Installation
22
+
23
+ ### 1. Install Dependencies
24
+
25
+ ```bash
26
+ cd client
27
+ npm install
28
+ ```
29
+
30
+ ### 2. Build the Client
31
+
32
+ ```bash
33
+ npm run build
34
+ ```
35
+
36
+ ### 3. Set Up Environment Variables
37
+
38
+ Create a `.env` file or export these variables:
39
+
40
+ ```bash
41
+ export GOOGLE_CLIENT_ID="your-client-id.apps.googleusercontent.com"
42
+ export GOOGLE_CLIENT_SECRET="your-client-secret"
43
+ ```
44
+
45
+ ### 4. Configure Claude Code MCP Settings
46
+
47
+ Add the client to your Claude Code MCP configuration file at `~/.config/claude/mcp_config.json`:
48
+
49
+ ```json
50
+ {
51
+ "mcpServers": {
52
+ "borg-mcp": {
53
+ "command": "node",
54
+ "args": [
55
+ "/absolute/path/to/mcp-claude-md-storage/client/dist/index.js"
56
+ ],
57
+ "env": {
58
+ "GOOGLE_CLIENT_ID": "your-client-id.apps.googleusercontent.com",
59
+ "GOOGLE_CLIENT_SECRET": "your-client-secret"
60
+ }
61
+ }
62
+ }
63
+ }
64
+ ```
65
+
66
+ **Important**: Replace `/absolute/path/to/` with the actual path to your project directory.
67
+
68
+ ## First-Time Setup
69
+
70
+ ### Authentication Flow
71
+
72
+ On first run, the client will prompt you to authenticate:
73
+
74
+ ```
75
+ 🔐 Borg MCP Authentication
76
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
77
+
78
+ 📱 Please visit: https://www.google.com/device
79
+ 🔑 And enter code: XXXX-XXXX
80
+
81
+ Waiting for authorization...
82
+ ```
83
+
84
+ 1. Visit the URL shown
85
+ 2. Enter the device code
86
+ 3. Authorize with your Google account
87
+ 4. Tokens are securely stored in your OS keychain
88
+
89
+ ### Subscription
90
+
91
+ After authentication, create a subscription:
92
+
93
+ ```
94
+ Tools available:
95
+ - subscribe: Create Stripe checkout session ($2/month, 7-day trial)
96
+ ```
97
+
98
+ Use the `subscribe` tool in Claude Code to get a checkout URL and complete payment.
99
+
100
+ ## How It Works
101
+
102
+ ### Initial Sync
103
+
104
+ On startup, the client performs an initial sync:
105
+
106
+ - If remote has content and local is empty → **pulls from cloud**
107
+ - If local has content and remote is empty → **pushes to cloud**
108
+ - If both exist and match → **no sync needed**
109
+ - If both exist and differ → **conflict detection**
110
+
111
+ ### Automatic File Watching
112
+
113
+ The client watches `~/.claude/CLAUDE.md` for changes:
114
+
115
+ - **Debouncing**: 1 second delay to batch rapid edits
116
+ - **Content Hashing**: Only syncs when content actually changes (ignores temp saves)
117
+ - **Real-time Upload**: Changes auto-sync to cloud within seconds
118
+
119
+ ### Conflict Resolution
120
+
121
+ If both local and remote files have been modified since last sync:
122
+
123
+ ```
124
+ ⚠️ SYNC CONFLICT DETECTED
125
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
126
+
127
+ Both local and remote CLAUDE.md have been modified.
128
+ Local file: ~/.claude/CLAUDE.md
129
+ Remote backup: ~/.claude/CLAUDE.md.conflict
130
+
131
+ Please resolve manually by choosing one version or merging them.
132
+ Delete the .conflict file once resolved.
133
+ ```
134
+
135
+ **Resolution Steps**:
136
+ 1. Compare `~/.claude/CLAUDE.md` (local) with `~/.claude/CLAUDE.md.conflict` (remote)
137
+ 2. Manually merge or choose one version
138
+ 3. Delete `.conflict` file when done
139
+
140
+ ## Available Tools
141
+
142
+ Once configured, these tools are available in Claude Code:
143
+
144
+ - **subscribe**: Create Stripe checkout session for $2/month (7-day free trial)
145
+ - **get_claude_md**: Manually fetch CLAUDE.md from cloud
146
+ - **set_claude_md**: Manually upload CLAUDE.md to cloud
147
+ - **delete_claude_md**: Delete CLAUDE.md from cloud
148
+ - **subscription_status**: Check your current subscription status
149
+ - **export_data**: Export all your data (GDPR compliance)
150
+ - **sync_now**: Force immediate sync with cloud
151
+
152
+ ## Troubleshooting
153
+
154
+ ### "Authentication required" Error
155
+
156
+ Your OAuth token has expired. Re-run the client and follow the authentication flow again.
157
+
158
+ ```bash
159
+ # Manually trigger re-auth by clearing tokens
160
+ node dist/index.js
161
+ ```
162
+
163
+ ### Sync Conflicts Not Resolving
164
+
165
+ 1. Check that `.conflict` file exists at `~/.claude/CLAUDE.md.conflict`
166
+ 2. Manually merge changes between local and conflict file
167
+ 3. Delete `.conflict` file to clear the conflict state
168
+
169
+ ### File Watcher Not Detecting Changes
170
+
171
+ - Verify `~/.claude/CLAUDE.md` path exists
172
+ - Check file permissions (must be readable/writable)
173
+ - Some editors use atomic writes (rename temp file) which may delay detection
174
+
175
+ ### Network Failures
176
+
177
+ The client automatically retries with exponential backoff:
178
+ - Retry 1: 1 second delay
179
+ - Retry 2: 2 second delay
180
+ - Retry 3: 4 second delay
181
+ - Max retries: 3
182
+
183
+ If network is down, edits are queued and will sync once connection is restored.
184
+
185
+ ## Security
186
+
187
+ ### Token Storage
188
+
189
+ OAuth tokens are stored using platform-specific secure credential managers:
190
+
191
+ - **macOS**: Keychain Access
192
+ - **Windows**: Credential Manager
193
+ - **Linux**: Secret Service API (libsecret)
194
+
195
+ Tokens are **never** written to disk in plain text.
196
+
197
+ ### Authentication
198
+
199
+ - Uses Google OAuth 2.0 device code flow (designed for CLI apps)
200
+ - ID tokens are automatically refreshed when expired
201
+ - Auth tokens are automatically injected into all remote API calls
202
+
203
+ ## Development
204
+
205
+ ### Running from Source
206
+
207
+ ```bash
208
+ npm run dev
209
+ ```
210
+
211
+ ### Building
212
+
213
+ ```bash
214
+ npm run build
215
+ ```
216
+
217
+ ### Testing
218
+
219
+ ```bash
220
+ npm test
221
+ ```
222
+
223
+ ## Architecture
224
+
225
+ ```
226
+ ┌─────────────────┐
227
+ │ Claude Code │
228
+ └────────┬────────┘
229
+ │ stdio MCP
230
+
231
+ ┌────────▼────────┐
232
+ │ Borg MCP │◄──┐
233
+ │ Client │ │ File Watcher
234
+ └────────┬────────┘ │ (chokidar)
235
+ │ │
236
+ │ ┌──▼──────────────┐
237
+ │ │ ~/.claude/ │
238
+ │ │ CLAUDE.md │
239
+ │ └─────────────────┘
240
+ │ HTTPS + Auth
241
+
242
+ ┌────────▼────────┐
243
+ │ api.borgmcp.ai │
244
+ │ (Remote Server) │
245
+ └─────────────────┘
246
+ ```
247
+
248
+ ## License
249
+
250
+ MIT
package/dist/auth.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Google OAuth 2.0 Authorization Code Flow with PKCE
3
+ * For Desktop/CLI applications
4
+ *
5
+ * Flow:
6
+ * 1. Generate PKCE code_verifier and code_challenge
7
+ * 2. Start local HTTP server for callback
8
+ * 3. Open browser to Google authorization URL
9
+ * 4. User authorizes in browser
10
+ * 5. Receive authorization code via localhost callback
11
+ * 6. Exchange code for tokens
12
+ * 7. Store tokens securely in OS keychain
13
+ */
14
+ /**
15
+ * Perform complete OAuth authorization code flow with PKCE
16
+ * Opens browser for user authorization
17
+ * Stores tokens in OS keychain on success
18
+ */
19
+ export declare function authenticateWithGoogle(): Promise<void>;
20
+ /**
21
+ * Refresh ID token using refresh token
22
+ */
23
+ export declare function refreshIdToken(refreshToken: string): Promise<void>;
24
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAsLH;;;;GAIG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC,CA+C5D;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CAsBf"}
package/dist/auth.js ADDED
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Google OAuth 2.0 Authorization Code Flow with PKCE
3
+ * For Desktop/CLI applications
4
+ *
5
+ * Flow:
6
+ * 1. Generate PKCE code_verifier and code_challenge
7
+ * 2. Start local HTTP server for callback
8
+ * 3. Open browser to Google authorization URL
9
+ * 4. User authorizes in browser
10
+ * 5. Receive authorization code via localhost callback
11
+ * 6. Exchange code for tokens
12
+ * 7. Store tokens securely in OS keychain
13
+ */
14
+ import { createServer } from 'http';
15
+ import { URL } from 'url';
16
+ import crypto from 'crypto';
17
+ import open from 'open';
18
+ import { storeIdToken, storeRefreshToken } from './config.js';
19
+ // Google OAuth Client credentials for Borg MCP CLI (Desktop app)
20
+ // Per Google's documentation: "the client secret is obviously not treated as a secret"
21
+ // for installed/desktop applications. This follows industry standard (AWS CLI, gcloud, GitHub CLI)
22
+ const GOOGLE_CLIENT_ID = '675073910799-41pbe12rfhqemidh64h09s4q3e0udpgp.apps.googleusercontent.com';
23
+ const GOOGLE_CLIENT_SECRET = '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';
24
+ const GOOGLE_AUTHORIZE_URL = 'https://accounts.google.com/o/oauth2/v2/auth';
25
+ const GOOGLE_TOKEN_URL = 'https://oauth2.googleapis.com/token';
26
+ const SCOPES = ['openid', 'email', 'profile'];
27
+ // Port range for dynamic port selection (8000-9000)
28
+ const PORT_RANGE_START = 8000;
29
+ const PORT_RANGE_END = 9000;
30
+ /**
31
+ * Generate PKCE code_verifier and code_challenge
32
+ * Uses SHA256 hashing per OAuth 2.0 PKCE spec (RFC 7636)
33
+ */
34
+ function generatePKCE() {
35
+ // Generate random code_verifier (43-128 characters)
36
+ const verifier = crypto.randomBytes(32).toString('base64url');
37
+ // Generate code_challenge = BASE64URL(SHA256(code_verifier))
38
+ const challenge = crypto
39
+ .createHash('sha256')
40
+ .update(verifier)
41
+ .digest('base64url');
42
+ return { verifier, challenge };
43
+ }
44
+ /**
45
+ * Find an available port in the specified range
46
+ */
47
+ async function findAvailablePort() {
48
+ return new Promise((resolve, reject) => {
49
+ // Try to bind to port 0 to let the OS assign a free port
50
+ const testServer = createServer();
51
+ testServer.listen(0, () => {
52
+ const address = testServer.address();
53
+ if (address && typeof address === 'object') {
54
+ const port = address.port;
55
+ testServer.close(() => resolve(port));
56
+ }
57
+ else {
58
+ testServer.close(() => reject(new Error('Failed to get assigned port')));
59
+ }
60
+ });
61
+ testServer.on('error', reject);
62
+ });
63
+ }
64
+ /**
65
+ * Start local HTTP server to receive OAuth callback
66
+ * Returns { server, port, codePromise }
67
+ */
68
+ async function startCallbackServer() {
69
+ // Find available port first
70
+ const port = await findAvailablePort();
71
+ const codePromise = new Promise((resolve, reject) => {
72
+ const server = createServer((req, res) => {
73
+ const url = new URL(req.url, `http://localhost:${port}`);
74
+ if (url.pathname === '/callback') {
75
+ const code = url.searchParams.get('code');
76
+ const error = url.searchParams.get('error');
77
+ if (error) {
78
+ res.writeHead(400, { 'Content-Type': 'text/html' });
79
+ res.end(`
80
+ <html>
81
+ <body>
82
+ <h1>❌ Authentication Failed</h1>
83
+ <p>Error: ${error}</p>
84
+ <p>You can close this window.</p>
85
+ </body>
86
+ </html>
87
+ `);
88
+ server.close();
89
+ reject(new Error(`OAuth error: ${error}`));
90
+ return;
91
+ }
92
+ if (code) {
93
+ res.writeHead(200, { 'Content-Type': 'text/html' });
94
+ res.end(`
95
+ <html>
96
+ <body>
97
+ <h1>✅ Authentication Successful!</h1>
98
+ <p>You can close this window and return to your terminal.</p>
99
+ </body>
100
+ </html>
101
+ `);
102
+ server.close();
103
+ resolve(code);
104
+ return;
105
+ }
106
+ res.writeHead(400, { 'Content-Type': 'text/html' });
107
+ res.end(`
108
+ <html>
109
+ <body>
110
+ <h1>❌ Invalid Request</h1>
111
+ <p>Missing authorization code.</p>
112
+ </body>
113
+ </html>
114
+ `);
115
+ server.close();
116
+ reject(new Error('Missing authorization code'));
117
+ }
118
+ });
119
+ server.listen(port, () => {
120
+ console.error(`Callback server listening on http://localhost:${port}`);
121
+ });
122
+ // Timeout after 5 minutes
123
+ setTimeout(() => {
124
+ server.close();
125
+ reject(new Error('Authentication timeout - no response received'));
126
+ }, 5 * 60 * 1000);
127
+ });
128
+ return { port, codePromise };
129
+ }
130
+ /**
131
+ * Exchange authorization code for tokens
132
+ */
133
+ async function exchangeCodeForTokens(code, codeVerifier, port) {
134
+ const redirectUri = `http://localhost:${port}/callback`;
135
+ const response = await fetch(GOOGLE_TOKEN_URL, {
136
+ method: 'POST',
137
+ headers: {
138
+ 'Content-Type': 'application/x-www-form-urlencoded',
139
+ },
140
+ body: new URLSearchParams({
141
+ client_id: GOOGLE_CLIENT_ID,
142
+ client_secret: GOOGLE_CLIENT_SECRET,
143
+ code,
144
+ code_verifier: codeVerifier,
145
+ grant_type: 'authorization_code',
146
+ redirect_uri: redirectUri,
147
+ }),
148
+ });
149
+ if (!response.ok) {
150
+ const error = await response.text();
151
+ throw new Error(`Failed to exchange code for tokens: ${error}`);
152
+ }
153
+ return (await response.json());
154
+ }
155
+ /**
156
+ * Perform complete OAuth authorization code flow with PKCE
157
+ * Opens browser for user authorization
158
+ * Stores tokens in OS keychain on success
159
+ */
160
+ export async function authenticateWithGoogle() {
161
+ console.error('\\n🔐 Borg MCP Authentication');
162
+ console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n');
163
+ // Step 1: Generate PKCE pair
164
+ console.error('Generating PKCE challenge...');
165
+ const pkce = generatePKCE();
166
+ // Step 2: Start local callback server (gets dynamic port)
167
+ console.error('Starting local callback server...');
168
+ const { port, codePromise } = await startCallbackServer();
169
+ // Step 3: Build authorization URL with dynamic redirect URI
170
+ const redirectUri = `http://localhost:${port}/callback`;
171
+ const authUrl = new URL(GOOGLE_AUTHORIZE_URL);
172
+ authUrl.searchParams.set('client_id', GOOGLE_CLIENT_ID);
173
+ authUrl.searchParams.set('redirect_uri', redirectUri);
174
+ authUrl.searchParams.set('response_type', 'code');
175
+ authUrl.searchParams.set('scope', SCOPES.join(' '));
176
+ authUrl.searchParams.set('code_challenge', pkce.challenge);
177
+ authUrl.searchParams.set('code_challenge_method', 'S256');
178
+ authUrl.searchParams.set('access_type', 'offline'); // Request refresh token
179
+ authUrl.searchParams.set('prompt', 'consent'); // Force consent to get refresh token
180
+ // Step 4: Open browser
181
+ console.error('\\n📱 Opening browser for authorization...');
182
+ console.error('If browser does not open, visit:');
183
+ console.error(`${authUrl.toString()}\\n`);
184
+ await open(authUrl.toString());
185
+ // Step 5: Wait for authorization code
186
+ console.error('Waiting for authorization...');
187
+ const code = await codePromise;
188
+ // Step 6: Exchange code for tokens
189
+ console.error('Exchanging authorization code for tokens...');
190
+ const tokenData = await exchangeCodeForTokens(code, pkce.verifier, port);
191
+ // Step 7: Store tokens securely
192
+ const expiresAt = Date.now() + tokenData.expires_in * 1000;
193
+ await storeIdToken(tokenData.id_token, expiresAt);
194
+ if (tokenData.refresh_token) {
195
+ await storeRefreshToken(tokenData.refresh_token);
196
+ }
197
+ console.error('\\n✅ Authentication successful!\\n');
198
+ }
199
+ /**
200
+ * Refresh ID token using refresh token
201
+ */
202
+ export async function refreshIdToken(refreshToken) {
203
+ const response = await fetch(GOOGLE_TOKEN_URL, {
204
+ method: 'POST',
205
+ headers: {
206
+ 'Content-Type': 'application/x-www-form-urlencoded',
207
+ },
208
+ body: new URLSearchParams({
209
+ client_id: GOOGLE_CLIENT_ID,
210
+ client_secret: GOOGLE_CLIENT_SECRET,
211
+ refresh_token: refreshToken,
212
+ grant_type: 'refresh_token',
213
+ }),
214
+ });
215
+ if (!response.ok) {
216
+ const error = await response.text();
217
+ throw new Error(`Failed to refresh token: ${error}`);
218
+ }
219
+ const data = (await response.json());
220
+ const expiresAt = Date.now() + data.expires_in * 1000;
221
+ await storeIdToken(data.id_token, expiresAt);
222
+ }
223
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * MCP Settings Configuration Utilities
3
+ *
4
+ * Handles adding borg-mcp to Claude Code via the claude CLI
5
+ */
6
+ /**
7
+ * Get absolute path to borgmcp index.js
8
+ * Returns the actual index.js file, not the npm symlink
9
+ */
10
+ export declare function getBinaryPath(): string;
11
+ /**
12
+ * Add borgmcp MCP server to Claude Code using claude CLI
13
+ * First removes any existing borgmcp configuration, then adds fresh one
14
+ * Runs: claude mcp remove --scope user borgmcp && claude mcp add --scope user borgmcp node "<path-to-index.js>"
15
+ */
16
+ export declare function addMcpServer(): void;
17
+ //# sourceMappingURL=config-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-utils.d.ts","sourceRoot":"","sources":["../src/config-utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAWH;;;GAGG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAItC;AAED;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,IAAI,CA2BnC"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * MCP Settings Configuration Utilities
3
+ *
4
+ * Handles adding borg-mcp to Claude Code via the claude CLI
5
+ */
6
+ import { execSync } from 'child_process';
7
+ import path from 'path';
8
+ import { fileURLToPath } from 'url';
9
+ import { dirname } from 'path';
10
+ // Get __dirname equivalent in ESM
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = dirname(__filename);
13
+ /**
14
+ * Get absolute path to borgmcp index.js
15
+ * Returns the actual index.js file, not the npm symlink
16
+ */
17
+ export function getBinaryPath() {
18
+ // In production: dist/index.js is in the same directory as this file
19
+ // In development: same
20
+ return path.join(__dirname, 'index.js');
21
+ }
22
+ /**
23
+ * Add borgmcp MCP server to Claude Code using claude CLI
24
+ * First removes any existing borgmcp configuration, then adds fresh one
25
+ * Runs: claude mcp remove --scope user borgmcp && claude mcp add --scope user borgmcp node "<path-to-index.js>"
26
+ */
27
+ export function addMcpServer() {
28
+ const indexPath = getBinaryPath();
29
+ try {
30
+ // First, remove any existing borgmcp configuration (ignore errors if not found)
31
+ try {
32
+ execSync('claude mcp remove --scope user borgmcp', { stdio: 'ignore' });
33
+ }
34
+ catch {
35
+ // Ignore - server might not exist yet
36
+ }
37
+ // Run claude mcp add command
38
+ const command = `claude mcp add --scope user borgmcp node "${indexPath}"`;
39
+ execSync(command, {
40
+ stdio: 'inherit', // Show output to user
41
+ env: {
42
+ ...process.env,
43
+ BORG_API_URL: process.env.BORG_API_URL || 'https://api.borgmcp.ai'
44
+ }
45
+ });
46
+ }
47
+ catch (error) {
48
+ if (error.message?.includes('command not found')) {
49
+ throw new Error('Claude CLI not found. Please install Claude Code first.');
50
+ }
51
+ throw new Error(`Failed to add MCP server: ${error.message}`);
52
+ }
53
+ }
54
+ //# sourceMappingURL=config-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-utils.js","sourceRoot":"","sources":["../src/config-utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAE/B,kCAAkC;AAClC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEtC;;;GAGG;AACH,MAAM,UAAU,aAAa;IAC3B,qEAAqE;IACrE,uBAAuB;IACvB,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;AAC1C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY;IAC1B,MAAM,SAAS,GAAG,aAAa,EAAE,CAAC;IAElC,IAAI,CAAC;QACH,gFAAgF;QAChF,IAAI,CAAC;YACH,QAAQ,CAAC,wCAAwC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC1E,CAAC;QAAC,MAAM,CAAC;YACP,sCAAsC;QACxC,CAAC;QAED,6BAA6B;QAC7B,MAAM,OAAO,GAAG,6CAA6C,SAAS,GAAG,CAAC;QAE1E,QAAQ,CAAC,OAAO,EAAE;YAChB,KAAK,EAAE,SAAS,EAAE,sBAAsB;YACxC,GAAG,EAAE;gBACH,GAAG,OAAO,CAAC,GAAG;gBACd,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,wBAAwB;aACnE;SACF,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,IAAI,KAAK,CAAC,OAAO,EAAE,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC;YACjD,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;QAC7E,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,6BAA6B,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IAChE,CAAC;AACH,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Secure token storage using OS keychain
3
+ * Uses Keytar for cross-platform credential management
4
+ *
5
+ * Addresses consensus finding: Machine-specific encryption is insecure
6
+ * Solution: Use OS native keychains (macOS Keychain, Windows Credential Manager, Linux Secret Service)
7
+ */
8
+ /**
9
+ * Store Google OAuth ID token securely in OS keychain
10
+ */
11
+ export declare function storeIdToken(idToken: string, expiresAt: number): Promise<void>;
12
+ /**
13
+ * Store Google OAuth refresh token securely in OS keychain
14
+ */
15
+ export declare function storeRefreshToken(refreshToken: string): Promise<void>;
16
+ /**
17
+ * Retrieve Google OAuth ID token from OS keychain
18
+ * Returns null if not found or expired
19
+ */
20
+ export declare function getIdToken(): Promise<string | null>;
21
+ /**
22
+ * Retrieve Google OAuth refresh token from OS keychain
23
+ */
24
+ export declare function getRefreshToken(): Promise<string | null>;
25
+ /**
26
+ * Clear all stored tokens from OS keychain
27
+ */
28
+ export declare function clearTokens(): Promise<void>;
29
+ /**
30
+ * Check if user has valid authentication
31
+ */
32
+ export declare function isAuthenticated(): Promise<boolean>;
33
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAUH;;GAEG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGpF;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE3E;AAED;;;GAGG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiBzD;AAED;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAE9D;AAED;;GAEG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAIjD;AAED;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC,CAGxD"}