#
tokens: 13912/50000 3/72 files (page 3/3)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 3 of 3. Use http://codebase.md/mohalmah/google-appscript-mcp-server?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .env.example
├── .gitattributes
├── .gitignore
├── .gitkeep
├── commands
│   └── tools.js
├── demo
│   └── google app script mcp demo.gif
├── Dockerfile
├── ERROR_HANDLING_ENHANCEMENT.md
├── helpers
│   ├── check-deployments.js
│   ├── check-head-deployment.js
│   ├── convert-to-oauth.js
│   ├── create-proper-webapp.js
│   ├── create-web-app-fixed.js
│   ├── create-web-app.js
│   └── create-webapp-deployment.js
├── index.js
├── lib
│   ├── authHelper.js
│   ├── logger.js
│   ├── oauth-helper.js
│   ├── tokenManager.js
│   └── tools.js
├── LOGGING.md
├── mcpServer.js
├── OAUTH_IMPLEMENTATION.md
├── OAUTH_SETUP.md
├── oauth-setup.js
├── package-lock.json
├── package.json
├── README.md
├── test
│   ├── debug-content-fetch.js
│   ├── debug-deployment.js
│   ├── debug-mcp-deployment.js
│   ├── debug-test.js
│   ├── deploy-complete-webapp.js
│   ├── oauth-setup-broken.js
│   ├── oauth-setup-fixed.js
│   ├── simple-oauth-test.js
│   ├── simple-test.js
│   ├── test-complete-mcp-webapp.js
│   ├── test-fields-issue.js
│   ├── test-logging.js
│   ├── test-mcp-content.js
│   ├── test-mcp-deployment-direct.js
│   ├── test-mcp-deployment-fix.js
│   ├── test-mcp-deployment-get.js
│   ├── test-mcp-errors.js
│   ├── test-mcp-fetch-processes.js
│   ├── test-mcp-processes.js
│   ├── test-mcp-tools.js
│   ├── test-mcp-version-fix.js
│   ├── test-oauth.js
│   ├── test-token-management.js
│   ├── test-versions-list.js
│   ├── update-and-deploy-dark-theme.js
│   ├── update-error-handling.js
│   ├── update-tools.js
│   └── update-webapp-deployment.js
└── tools
    ├── google-app-script-api
    │   └── apps-script-api
    │       ├── script-processes-list-script-processes.js
    │       ├── script-processes-list.js
    │       ├── script-projects-create.js
    │       ├── script-projects-deployments-create.js
    │       ├── script-projects-deployments-delete.js
    │       ├── script-projects-deployments-get.js
    │       ├── script-projects-deployments-list.js
    │       ├── script-projects-deployments-update.js
    │       ├── script-projects-get-content.js
    │       ├── script-projects-get-metrics.js
    │       ├── script-projects-get.js
    │       ├── script-projects-update-content.js
    │       ├── script-projects-versions-create.js
    │       ├── script-projects-versions-get.js
    │       ├── script-projects-versions-list.js
    │       └── script-scripts-run.js
    └── paths.js
```

# Files

--------------------------------------------------------------------------------
/test/update-and-deploy-dark-theme.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * Update script content to dark theme and deploy as web app
  5 |  */
  6 | 
  7 | import { getOAuthAccessToken } from '../lib/oauth-helper.js';
  8 | 
  9 | const scriptId = '1fSY7y3Rh84FsgJmrFIMm4AUOV3mPgelLRvZ4Dahrv68zyDzX-cGbeYjn';
 10 | 
 11 | // Dark theme HTML content
 12 | const darkThemeHTML = `<!DOCTYPE html>
 13 | <html>
 14 | <head>
 15 |   <base target="_top">
 16 |   <title>Hello World App via MCP - Dark Theme</title>
 17 |   <style>
 18 |     body {
 19 |       font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
 20 |       max-width: 600px;
 21 |       margin: 50px auto;
 22 |       padding: 20px;
 23 |       background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
 24 |       color: #e0e0e0;
 25 |       text-align: center;
 26 |       min-height: 100vh;
 27 |       box-sizing: border-box;
 28 |     }
 29 |     .container {
 30 |       background: rgba(255, 255, 255, 0.05);
 31 |       border: 1px solid rgba(255, 255, 255, 0.1);
 32 |       border-radius: 20px;
 33 |       padding: 40px;
 34 |       backdrop-filter: blur(15px);
 35 |       box-shadow: 0 12px 40px 0 rgba(0, 0, 0, 0.3);
 36 |     }
 37 |     h1 {
 38 |       color: #ffffff;
 39 |       margin-bottom: 30px;
 40 |       font-size: 2.5em;
 41 |       text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
 42 |     }
 43 |     p {
 44 |       color: #b0b0b0;
 45 |       font-size: 1.1em;
 46 |       margin-bottom: 30px;
 47 |     }
 48 |     input {
 49 |       padding: 14px;
 50 |       margin: 10px;
 51 |       border: 2px solid #444;
 52 |       border-radius: 10px;
 53 |       font-size: 16px;
 54 |       width: 250px;
 55 |       background: #2a2a2a;
 56 |       color: #e0e0e0;
 57 |       transition: border-color 0.3s, box-shadow 0.3s;
 58 |     }
 59 |     input:focus {
 60 |       outline: none;
 61 |       border-color: #64b5f6;
 62 |       box-shadow: 0 0 0 3px rgba(100, 181, 246, 0.2);
 63 |     }
 64 |     input::placeholder {
 65 |       color: #888;
 66 |     }
 67 |     button {
 68 |       background: linear-gradient(135deg, #2196f3 0%, #1976d2 100%);
 69 |       color: white;
 70 |       border: none;
 71 |       padding: 14px 28px;
 72 |       margin: 10px;
 73 |       border-radius: 10px;
 74 |       cursor: pointer;
 75 |       font-size: 16px;
 76 |       font-weight: 600;
 77 |       transition: all 0.3s;
 78 |       box-shadow: 0 4px 15px rgba(33, 150, 243, 0.3);
 79 |     }
 80 |     button:hover {
 81 |       background: linear-gradient(135deg, #1976d2 0%, #1565c0 100%);
 82 |       transform: translateY(-2px);
 83 |       box-shadow: 0 6px 20px rgba(33, 150, 243, 0.4);
 84 |     }
 85 |     button:active {
 86 |       transform: translateY(0);
 87 |     }
 88 |     .result {
 89 |       margin-top: 25px;
 90 |       padding: 25px;
 91 |       background: rgba(255, 255, 255, 0.08);
 92 |       border: 1px solid rgba(255, 255, 255, 0.12);
 93 |       border-radius: 12px;
 94 |       min-height: 60px;
 95 |       display: flex;
 96 |       align-items: center;
 97 |       justify-content: center;
 98 |       color: #f0f0f0;
 99 |       font-size: 18px;
100 |     }
101 |     .loading {
102 |       display: none;
103 |       color: #64b5f6;
104 |       font-size: 18px;
105 |       font-weight: 600;
106 |     }
107 |     .timestamp {
108 |       margin-top: 15px;
109 |       font-size: 0.9em;
110 |       color: #888;
111 |       font-style: italic;
112 |     }
113 |     .emoji {
114 |       font-size: 1.2em;
115 |       margin: 0 5px;
116 |     }
117 |     @keyframes pulse {
118 |       0% { opacity: 0.6; }
119 |       50% { opacity: 1; }
120 |       100% { opacity: 0.6; }
121 |     }
122 |     .loading {
123 |       animation: pulse 1.5s infinite;
124 |     }
125 |   </style>
126 | </head>
127 | <body>
128 |   <div class="container">
129 |     <h1><span class="emoji">🌙</span> Dark Theme MCP App <span class="emoji">🌙</span></h1>
130 |     <p>This sleek dark-themed web app was created using MCP (Model Context Protocol) tools!</p>
131 |     
132 |     <div class="input-group">
133 |       <input type="text" id="nameInput" placeholder="Enter your name (optional)" />
134 |     </div>
135 |     
136 |     <div>
137 |       <button onclick="sayHello()">🗨️ Say Hello</button>
138 |       <button onclick="getTime()">🕒 Get Current Time</button>
139 |     </div>
140 |     
141 |     <div id="result" class="result">
142 |       Click a button to experience the dark MCP magic! ✨
143 |     </div>
144 |     
145 |     <div id="loading" class="loading">⏳ Loading...</div>
146 |     
147 |     <div id="timestamp" class="timestamp"></div>
148 |   </div>
149 | 
150 |   <script>
151 |     function sayHello() {
152 |       showLoading();
153 |       const name = document.getElementById('nameInput').value;
154 |       
155 |       google.script.run
156 |         .withSuccessHandler(onSuccess)
157 |         .withFailureHandler(onFailure)
158 |         .getGreeting(name);
159 |     }
160 |     
161 |     function getTime() {
162 |       showLoading();
163 |       
164 |       google.script.run
165 |         .withSuccessHandler(onTimeSuccess)
166 |         .withFailureHandler(onFailure)
167 |         .getCurrentTime();
168 |     }
169 |     
170 |     function onSuccess(result) {
171 |       hideLoading();
172 |       document.getElementById('result').innerHTML = \`
173 |         <div style="font-size: 20px; font-weight: bold; color: #64b5f6;">
174 |           \${result}
175 |         </div>
176 |       \`;
177 |       updateTimestamp();
178 |     }
179 |     
180 |     function onTimeSuccess(result) {
181 |       hideLoading();
182 |       document.getElementById('result').innerHTML = \`
183 |         <div style="font-size: 18px; color: #81c784;">
184 |           🕒 Current Time: <strong style="color: #fff;">\${result}</strong>
185 |         </div>
186 |       \`;
187 |       updateTimestamp();
188 |     }
189 |     
190 |     function onFailure(error) {
191 |       hideLoading();
192 |       document.getElementById('result').innerHTML = \`
193 |         <div style="color: #f48fb1;">
194 |           ❌ Error: \${error.message || 'Something went wrong!'}
195 |         </div>
196 |       \`;
197 |       updateTimestamp();
198 |     }
199 |     
200 |     function showLoading() {
201 |       document.getElementById('loading').style.display = 'block';
202 |       document.getElementById('result').style.display = 'none';
203 |     }
204 |     
205 |     function hideLoading() {
206 |       document.getElementById('loading').style.display = 'none';
207 |       document.getElementById('result').style.display = 'flex';
208 |     }
209 |     
210 |     function updateTimestamp() {
211 |       document.getElementById('timestamp').textContent = 
212 |         'Last updated: ' + new Date().toLocaleTimeString();
213 |     }
214 |     
215 |     // Initial timestamp
216 |     updateTimestamp();
217 |     
218 |     // Add some interactive effects
219 |     document.addEventListener('DOMContentLoaded', function() {
220 |       const buttons = document.querySelectorAll('button');
221 |       buttons.forEach(button => {
222 |         button.addEventListener('mouseenter', function() {
223 |           this.style.transform = 'translateY(-2px) scale(1.02)';
224 |         });
225 |         button.addEventListener('mouseleave', function() {
226 |           this.style.transform = 'translateY(0) scale(1)';
227 |         });
228 |       });
229 |     });
230 |   </script>
231 | </body>
232 | </html>`;
233 | 
234 | // Updated server-side code
235 | const serverCode = `/**
236 |  * Serves the HTML page when the web app is accessed
237 |  */
238 | function doGet() {
239 |   return HtmlService.createHtmlOutputFromFile('index')
240 |     .setTitle('Hello World App via MCP - Dark Theme')
241 |     .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
242 | }
243 | 
244 | /**
245 |  * Server-side function that can be called from the client
246 |  */
247 | function getGreeting(name) {
248 |   if (!name) {
249 |     name = 'World';
250 |   }
251 |   return \`Hello, \${name}! This dark-themed web app was created via MCP tools.\`;
252 | }
253 | 
254 | /**
255 |  * Get current time
256 |  */
257 | function getCurrentTime() {
258 |   return new Date().toLocaleString();
259 | }`;
260 | 
261 | // Configuration
262 | const appsScriptConfig = `{
263 |   "timeZone": "America/New_York",
264 |   "dependencies": {},
265 |   "exceptionLogging": "STACKDRIVER",
266 |   "runtimeVersion": "V8",
267 |   "webapp": {
268 |     "access": "ANYONE",
269 |     "executeAs": "USER_ACCESSING"
270 |   }
271 | }`;
272 | 
273 | async function updateScriptContent() {
274 |   try {
275 |     const token = await getOAuthAccessToken();
276 |     console.log('🔄 Updating script content with dark theme...');
277 |     
278 |     const files = [
279 |       {
280 |         name: "appsscript",
281 |         type: "JSON",
282 |         source: appsScriptConfig
283 |       },
284 |       {
285 |         name: "code",
286 |         type: "SERVER_JS", 
287 |         source: serverCode
288 |       },
289 |       {
290 |         name: "index",
291 |         type: "HTML",
292 |         source: darkThemeHTML
293 |       }
294 |     ];
295 | 
296 |     const response = await fetch(`https://script.googleapis.com/v1/projects/${scriptId}/content`, {
297 |       method: 'PUT',
298 |       headers: {
299 |         'Authorization': `Bearer ${token}`,
300 |         'Content-Type': 'application/json',
301 |         'Accept': 'application/json'
302 |       },
303 |       body: JSON.stringify({ files })
304 |     });
305 | 
306 |     if (!response.ok) {
307 |       const errorText = await response.text();
308 |       console.error('❌ Error updating content:', errorText);
309 |       throw new Error(`HTTP ${response.status}: ${errorText}`);
310 |     }
311 | 
312 |     const data = await response.json();
313 |     console.log('✅ Script content updated successfully!');
314 |     return data;
315 |   } catch (error) {
316 |     console.error('❌ Error updating script content:', error);
317 |     return null;
318 |   }
319 | }
320 | 
321 | async function createVersion() {
322 |   try {
323 |     const token = await getOAuthAccessToken();
324 |     console.log('📦 Creating new version...');
325 |     
326 |     const versionData = {
327 |       description: "Dark theme version via MCP"
328 |     };
329 | 
330 |     const response = await fetch(`https://script.googleapis.com/v1/projects/${scriptId}/versions`, {
331 |       method: 'POST',
332 |       headers: {
333 |         'Authorization': `Bearer ${token}`,
334 |         'Content-Type': 'application/json',
335 |         'Accept': 'application/json'
336 |       },
337 |       body: JSON.stringify(versionData)
338 |     });
339 | 
340 |     if (!response.ok) {
341 |       const errorText = await response.text();
342 |       console.error('❌ Error creating version:', errorText);
343 |       throw new Error(`HTTP ${response.status}: ${errorText}`);
344 |     }
345 | 
346 |     const data = await response.json();
347 |     console.log(`✅ Version ${data.versionNumber} created successfully!`);
348 |     return data;
349 |   } catch (error) {
350 |     console.error('❌ Error creating version:', error);
351 |     return null;
352 |   }
353 | }
354 | 
355 | async function createDeployment(versionNumber) {
356 |   try {
357 |     const token = await getOAuthAccessToken();
358 |     console.log('🚀 Creating web app deployment...');
359 |     
360 |     const deploymentConfig = {
361 |       description: "Dark Theme MCP Web App - Public Access",
362 |       manifestFileName: "appsscript",
363 |       versionNumber: versionNumber
364 |     };
365 | 
366 |     const response = await fetch(`https://script.googleapis.com/v1/projects/${scriptId}/deployments`, {
367 |       method: 'POST',
368 |       headers: {
369 |         'Authorization': `Bearer ${token}`,
370 |         'Content-Type': 'application/json',
371 |         'Accept': 'application/json'
372 |       },
373 |       body: JSON.stringify(deploymentConfig)
374 |     });
375 | 
376 |     if (!response.ok) {
377 |       const errorText = await response.text();
378 |       console.error('❌ Error creating deployment:', errorText);
379 |       throw new Error(`HTTP ${response.status}: ${errorText}`);
380 |     }
381 | 
382 |     const data = await response.json();
383 |     console.log('✅ Deployment created successfully!');
384 |     
385 |     // Extract web app URL
386 |     if (data.entryPoints && data.entryPoints[0] && data.entryPoints[0].webApp) {
387 |       const webAppUrl = data.entryPoints[0].webApp.url;
388 |       console.log('🌐 Web App URL:', webAppUrl);
389 |     }
390 |     
391 |     console.log('📋 Deployment Details:', JSON.stringify(data, null, 2));
392 |     return data;
393 |   } catch (error) {
394 |     console.error('❌ Error creating deployment:', error);
395 |     return null;
396 |   }
397 | }
398 | 
399 | async function main() {
400 |   console.log('🌙 Starting dark theme update and deployment...');
401 |   console.log('='.repeat(60));
402 |   
403 |   // Step 1: Update script content
404 |   const updateResult = await updateScriptContent();
405 |   if (!updateResult) {
406 |     console.log('❌ Failed to update content. Stopping.');
407 |     return;
408 |   }
409 |   
410 |   console.log('\n' + '='.repeat(60) + '\n');
411 |   
412 |   // Step 2: Create new version
413 |   const versionResult = await createVersion();
414 |   if (!versionResult) {
415 |     console.log('❌ Failed to create version. Stopping.');
416 |     return;
417 |   }
418 |   
419 |   console.log('\n' + '='.repeat(60) + '\n');
420 |   
421 |   // Step 3: Create deployment
422 |   const deploymentResult = await createDeployment(versionResult.versionNumber);
423 |   if (!deploymentResult) {
424 |     console.log('❌ Failed to create deployment. Stopping.');
425 |     return;
426 |   }
427 |   
428 |   console.log('\n🎉 Process completed successfully!');
429 |   console.log('Your dark-themed web app is now deployed and accessible.');
430 | }
431 | 
432 | main().catch(console.error);
433 | 
```

--------------------------------------------------------------------------------
/lib/oauth-helper.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * OAuth authentication helper for Google Apps Script API
  3 |  */
  4 | 
  5 | import 'dotenv/config';
  6 | import { google } from 'googleapis';
  7 | import { createServer } from 'http';
  8 | import open from 'open';
  9 | import { URL } from 'url';
 10 | import { createConnection } from 'net';
 11 | import { TokenManager } from './tokenManager.js';
 12 | import { logger } from './logger.js';
 13 | 
 14 | // Configuration - Comprehensive scopes for all Google APIs
 15 | const SCOPES = [
 16 |   // Google Apps Script API - Full access
 17 |   'https://www.googleapis.com/auth/script.projects',
 18 |   'https://www.googleapis.com/auth/script.projects.readonly',
 19 |   'https://www.googleapis.com/auth/script.deployments',
 20 |   'https://www.googleapis.com/auth/script.deployments.readonly',
 21 |   'https://www.googleapis.com/auth/script.metrics',
 22 |   'https://www.googleapis.com/auth/script.processes',
 23 |   'https://www.googleapis.com/auth/script.webapp.deploy'
 24 |   
 25 |   
 26 | ];
 27 | 
 28 | const REDIRECT_URI = 'http://localhost:3001/oauth/callback';
 29 | const PORT = 3001;
 30 | 
 31 | // Token manager instance
 32 | const tokenManager = new TokenManager();
 33 | 
 34 | /**
 35 |  * Finds an available port starting from the given port
 36 |  * @param {number} startPort - Port to start checking from
 37 |  * @returns {Promise<number>} Available port number
 38 |  */
 39 | async function findAvailablePort(startPort = PORT) {
 40 |   return new Promise((resolve) => {
 41 |     const server = createServer();
 42 |     
 43 |     server.listen(startPort, () => {
 44 |       const port = server.address().port;
 45 |       server.close(() => {
 46 |         resolve(port);
 47 |       });
 48 |     });
 49 |     
 50 |     server.on('error', () => {
 51 |       findAvailablePort(startPort + 1).then(resolve);
 52 |     });
 53 |   });
 54 | }
 55 | 
 56 | /**
 57 |  * Creates and configures OAuth2 client
 58 |  * @returns {OAuth2Client} Configured OAuth2 client
 59 |  */
 60 | function createOAuth2Client() {
 61 |   const clientId = process.env.GOOGLE_APP_SCRIPT_API_CLIENT_ID;
 62 |   const clientSecret = process.env.GOOGLE_APP_SCRIPT_API_CLIENT_SECRET;
 63 | 
 64 |   if (!clientId || !clientSecret) {
 65 |     logger.error('AUTH', 'Missing required OAuth credentials', {
 66 |       hasClientId: !!clientId,
 67 |       hasClientSecret: !!clientSecret
 68 |     });
 69 |     throw new Error('Missing required OAuth credentials: GOOGLE_APP_SCRIPT_API_CLIENT_ID and GOOGLE_APP_SCRIPT_API_CLIENT_SECRET must be set in environment variables');
 70 |   }
 71 | 
 72 |   logger.info('AUTH', 'Creating OAuth2 client', {
 73 |     clientId: clientId.substring(0, 20) + '...',
 74 |     redirectUri: REDIRECT_URI,
 75 |     scopeCount: SCOPES.length,
 76 |     scopes: SCOPES
 77 |   });
 78 | 
 79 |   console.log('🔐 Creating OAuth2 client...');
 80 |   console.log('   - Client ID:', clientId);
 81 |   console.log('   - Redirect URI:', REDIRECT_URI);
 82 |   console.log('   - Scopes:', SCOPES.length, 'permissions');
 83 | 
 84 |   return new google.auth.OAuth2(clientId, clientSecret, REDIRECT_URI);
 85 | }
 86 | 
 87 | /**
 88 |  * Starts OAuth flow with browser automation
 89 |  * @returns {Promise<Object>} OAuth tokens
 90 |  */
 91 | async function startOAuthFlow() {
 92 |   console.log('🚀 Starting OAuth flow...');
 93 |   
 94 |   const oAuth2Client = createOAuth2Client();
 95 |   
 96 |   return new Promise(async (resolve, reject) => {
 97 |     try {
 98 |       // Use the exact port that matches Google Cloud Console configuration
 99 |       const callbackPort = PORT; // Must match Google Cloud Console redirect URI
100 |       
101 |       console.log(`🌐 Starting OAuth callback server on port ${callbackPort}`);
102 |       console.log(`🔗 Redirect URI: ${REDIRECT_URI}`);
103 |       
104 |       // Create temporary HTTP server for OAuth callback
105 |       const server = createServer(async (req, res) => {
106 |         console.log('📥 OAuth callback received:', req.url);
107 |         
108 |         try {
109 |           const url = new URL(req.url, `http://localhost:${callbackPort}`);
110 |           
111 |           if (url.pathname === '/oauth/callback') {
112 |             const code = url.searchParams.get('code');
113 |             const error = url.searchParams.get('error');
114 |             
115 |             if (error) {
116 |               console.error('❌ OAuth error:', error);
117 |               res.writeHead(400, { 'Content-Type': 'text/html' });
118 |               res.end(`
119 |                 <html>
120 |                   <body style="font-family: Arial, sans-serif; padding: 50px; text-align: center;">
121 |                     <h2 style="color: #dc3545;">❌ Authentication Failed</h2>
122 |                     <p>Error: ${error}</p>
123 |                     <p>You can close this window.</p>
124 |                   </body>
125 |                 </html>
126 |               `);
127 |               server.close();
128 |               reject(new Error(`OAuth error: ${error}`));
129 |               return;
130 |             }
131 |             
132 |             if (!code) {
133 |               console.error('❌ No authorization code received');
134 |               res.writeHead(400, { 'Content-Type': 'text/html' });
135 |               res.end(`
136 |                 <html>
137 |                   <body style="font-family: Arial, sans-serif; padding: 50px; text-align: center;">
138 |                     <h2 style="color: #dc3545;">❌ No Authorization Code</h2>
139 |                     <p>No authorization code received from Google.</p>
140 |                     <p>You can close this window.</p>
141 |                   </body>
142 |                 </html>
143 |               `);
144 |               server.close();
145 |               reject(new Error('No authorization code received'));
146 |               return;
147 |             }
148 |             
149 |             console.log('🔄 Exchanging authorization code for tokens...');
150 |             console.log('🔑 Authorization code:', code.substring(0, 20) + '...');
151 |               try {
152 |               const { tokens: newTokens } = await oAuth2Client.getToken(code);
153 |               
154 |               console.log('✅ Token exchange successful!');
155 |               console.log('🎟️ Token details:');
156 |               console.log('   - Access token:', newTokens.access_token ? '✅ Received' : '❌ Missing');
157 |               console.log('   - Refresh token:', newTokens.refresh_token ? '✅ Received' : '❌ Missing');
158 |               console.log('   - Token type:', newTokens.token_type || 'Not specified');
159 |               console.log('   - Expires in:', newTokens.expiry_date ? new Date(newTokens.expiry_date).toISOString() : 'No expiry');
160 |               console.log('   - Scope:', newTokens.scope || 'Not specified');
161 |               
162 |               // Success response
163 |               res.writeHead(200, { 'Content-Type': 'text/html' });
164 |               res.end(`
165 |                 <html>
166 |                   <body style="font-family: Arial, sans-serif; padding: 50px; text-align: center;">
167 |                     <h2 style="color: #28a745;">✅ Authentication Successful!</h2>
168 |                     <p>You have been successfully authenticated with Google Apps Script API.</p>
169 |                     <p><strong>Access Token:</strong> ${newTokens.access_token ? 'Received ✅' : 'Missing ❌'}</p>
170 |                     <p><strong>Refresh Token:</strong> ${newTokens.refresh_token ? 'Received ✅' : 'Missing ❌'}</p>
171 |                     <p><strong>You can now close this window and return to your application.</strong></p>
172 |                     <script>
173 |                       setTimeout(() => {
174 |                         window.close();
175 |                       }, 5000);
176 |                     </script>
177 |                   </body>
178 |                 </html>
179 |               `);
180 |               
181 |               server.close();
182 |               resolve(newTokens);
183 |               
184 |             } catch (tokenError) {
185 |               console.error('❌ Error exchanging code for tokens:', tokenError);
186 |               res.writeHead(500, { 'Content-Type': 'text/html' });
187 |               res.end(`
188 |                 <html>
189 |                   <body style="font-family: Arial, sans-serif; padding: 50px; text-align: center;">
190 |                     <h2 style="color: #dc3545;">❌ Token Exchange Failed</h2>
191 |                     <p>Failed to exchange authorization code for tokens.</p>
192 |                     <pre style="text-align: left; background: #f8f9fa; padding: 20px;">${tokenError.message}</pre>
193 |                     <p>You can close this window.</p>
194 |                   </body>
195 |                 </html>
196 |                 `);
197 |               server.close();
198 |               reject(tokenError);
199 |             }
200 |           } else {
201 |             // Handle other paths
202 |             res.writeHead(404, { 'Content-Type': 'text/plain' });
203 |             res.end('Not Found');
204 |           }
205 |         } catch (err) {
206 |           console.error('❌ Server error:', err);
207 |           res.writeHead(500, { 'Content-Type': 'text/plain' });
208 |           res.end('Internal Server Error');
209 |           server.close();
210 |           reject(err);
211 |         }
212 |       });
213 | 
214 |       // Start server on the specific port
215 |       server.listen(callbackPort, () => {
216 |         console.log(`🌐 OAuth callback server started on port ${callbackPort}`);
217 |         
218 |         // Generate authorization URL
219 |         const authUrl = oAuth2Client.generateAuthUrl({
220 |           access_type: 'offline',
221 |           scope: SCOPES,
222 |           prompt: 'consent'
223 |         });
224 |         
225 |         console.log('🔗 Opening OAuth URL in browser...');
226 |         console.log('📋 OAuth URL:', authUrl);
227 |         
228 |         // Open browser
229 |         open(authUrl).catch(err => {
230 |           console.error('❌ Failed to open browser:', err);
231 |           console.log('🔗 Please manually open this URL in your browser:');
232 |           console.log(authUrl);
233 |         });
234 |       });
235 | 
236 |       // Handle server errors
237 |       server.on('error', (err) => {
238 |         console.error('❌ Server error:', err);
239 |         if (err.code === 'EADDRINUSE') {
240 |           reject(new Error(`Port ${callbackPort} is already in use. Please close other applications using this port or update your Google Cloud Console redirect URI to use a different port.`));
241 |         } else {
242 |           reject(err);
243 |         }
244 |       });
245 | 
246 |       // Timeout after 5 minutes
247 |       setTimeout(() => {
248 |         server.close();
249 |         reject(new Error('OAuth flow timed out after 5 minutes'));
250 |       }, 5 * 60 * 1000);
251 |       
252 |     } catch (error) {
253 |       reject(error);
254 |     }
255 |   });
256 | }
257 | 
258 | /**
259 |  * Gets an OAuth access token using TokenManager
260 |  * @returns {Promise<string>} Access token
261 |  */
262 | export async function getOAuthAccessToken() {
263 |   logger.info('AUTH', 'Requesting OAuth access token');
264 |   console.log('🔐 Getting OAuth access token...');
265 |   
266 |   const clientId = process.env.GOOGLE_APP_SCRIPT_API_CLIENT_ID;
267 |   const clientSecret = process.env.GOOGLE_APP_SCRIPT_API_CLIENT_SECRET;
268 | 
269 |   if (!clientId || !clientSecret) {
270 |     logger.error('AUTH', 'Missing OAuth credentials', {
271 |       hasClientId: !!clientId,
272 |       hasClientSecret: !!clientSecret
273 |     });
274 |     throw new Error('Missing required OAuth credentials: GOOGLE_APP_SCRIPT_API_CLIENT_ID and GOOGLE_APP_SCRIPT_API_CLIENT_SECRET must be set in environment variables');
275 |   }
276 |   
277 |   try {
278 |     const startTime = Date.now();
279 |     logger.debug('AUTH', 'Getting valid access token from token manager');
280 |     
281 |     const accessToken = await tokenManager.getValidAccessToken(clientId, clientSecret);
282 |     const duration = Date.now() - startTime;
283 |     
284 |     logger.info('AUTH', 'Access token obtained successfully', {
285 |       duration: `${duration}ms`,
286 |       tokenLength: accessToken ? accessToken.length : 0,
287 |       tokenPrefix: accessToken ? accessToken.substring(0, 10) + '...' : null
288 |     });
289 |     
290 |     console.log('✅ Access token obtained successfully');
291 |     return accessToken;
292 |   } catch (error) {
293 |     logger.error('AUTH', 'Failed to obtain access token', {
294 |       error: {
295 |         message: error.message,
296 |         stack: error.stack
297 |       },
298 |       hasStoredTokens: tokenManager.hasStoredTokens()
299 |     });
300 |     
301 |     if (error.message.includes('No tokens found')) {
302 |       console.error('❌ No OAuth tokens found.');
303 |       console.log('💡 Please run the OAuth setup first:');
304 |       console.log('   node oauth-setup.js');
305 |       throw new Error('OAuth tokens not found. Please run: node oauth-setup.js');
306 |     }
307 |     
308 |     console.error('❌ Error getting access token:', error);
309 |     throw error;
310 |   }
311 | }
312 | 
313 | /**
314 |  * Helper function to get authorization headers for API requests
315 |  * @returns {Promise<Object>} Headers object with Authorization
316 |  */
317 | export async function getAuthHeaders() {
318 |   logger.debug('AUTH', 'Creating authorization headers');
319 |   console.log('📋 Creating authorization headers...');
320 |   
321 |   const accessToken = await getOAuthAccessToken();
322 |   
323 |   const headers = {
324 |     'Authorization': `Bearer ${accessToken}`,
325 |     'Accept': 'application/json',
326 |     'Content-Type': 'application/json'
327 |   };
328 |   
329 |   logger.debug('AUTH', 'Authorization headers created', {
330 |     hasAuthorization: !!headers.Authorization,
331 |     authHeaderLength: headers.Authorization ? headers.Authorization.length : 0,
332 |     contentType: headers['Content-Type'],
333 |     accept: headers.Accept
334 |   });
335 |   
336 |   console.log('✅ Authorization headers created successfully');
337 |   return headers;
338 | }
339 | 
340 | /**
341 |  * Manually trigger OAuth flow (useful for testing)
342 |  * @returns {Promise<Object>} OAuth tokens
343 |  */
344 | export async function manualOAuthFlow() {
345 |   console.log('🔄 Starting manual OAuth flow...');
346 |   const tokens = await startOAuthFlow();
347 |   
348 |   // Save tokens using TokenManager
349 |   tokenManager.saveTokens(tokens);
350 |   
351 |   return tokens;
352 | }
353 | 
354 | /**
355 |  * Check if we have valid tokens
356 |  * @returns {boolean} True if tokens are available
357 |  */
358 | export function hasValidTokens() {
359 |   return tokenManager.hasStoredTokens();
360 | }
361 | 
362 | /**
363 |  * Clear stored tokens (logout)
364 |  */
365 | export function clearTokens() {
366 |   console.log('🚪 Clearing stored tokens...');
367 |   tokenManager.clearTokens();
368 |   console.log('✅ Tokens cleared successfully');
369 | }
370 | 
371 | /**
372 |  * Get current token information (for debugging)
373 |  * @returns {Object|null} Current token info
374 |  */
375 | export function getTokenInfo() {
376 |   return tokenManager.getTokenInfo();
377 | }
378 | 
```

--------------------------------------------------------------------------------
/oauth-setup.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * OAuth Setup Script for Google Apps Script API
  5 |  * This script helps you obtain and securely store OAuth tokens
  6 |  * Enhanced with detailed logging for debugging and monitoring
  7 |  */
  8 | 
  9 | import 'dotenv/config';
 10 | import { manualOAuthFlow } from './lib/oauth-helper.js';
 11 | import { TokenManager } from './lib/tokenManager.js';
 12 | import { readFileSync } from 'fs';
 13 | import { fileURLToPath } from 'url';
 14 | import { dirname } from 'path';
 15 | import os from 'os';
 16 | 
 17 | // Enhanced logging utilities
 18 | const log = {
 19 |   info: (msg, data = null) => {
 20 |     const timestamp = new Date().toISOString();
 21 |     console.log(`[${timestamp}] ℹ️  ${msg}`);
 22 |     if (data) console.log(`[${timestamp}] 📊 Data:`, data);
 23 |   },
 24 |   success: (msg, data = null) => {
 25 |     const timestamp = new Date().toISOString();
 26 |     console.log(`[${timestamp}] ✅ ${msg}`);
 27 |     if (data) console.log(`[${timestamp}] 📊 Data:`, data);
 28 |   },
 29 |   error: (msg, error = null) => {
 30 |     const timestamp = new Date().toISOString();
 31 |     console.error(`[${timestamp}] ❌ ${msg}`);
 32 |     if (error) {
 33 |       console.error(`[${timestamp}] 🐛 Error details:`, error.message);
 34 |       if (error.stack) console.error(`[${timestamp}] 📚 Stack trace:`, error.stack);
 35 |     }
 36 |   },
 37 |   warn: (msg, data = null) => {
 38 |     const timestamp = new Date().toISOString();
 39 |     console.warn(`[${timestamp}] ⚠️  ${msg}`);
 40 |     if (data) console.warn(`[${timestamp}] 📊 Data:`, data);
 41 |   },
 42 |   debug: (msg, data = null) => {
 43 |     if (process.env.DEBUG || process.argv.includes('--debug')) {
 44 |       const timestamp = new Date().toISOString();
 45 |       console.log(`[${timestamp}] 🔍 DEBUG: ${msg}`);
 46 |       if (data) console.log(`[${timestamp}] 🔍 DEBUG Data:`, JSON.stringify(data, null, 2));
 47 |     }
 48 |   },
 49 |   step: (step, total, msg) => {
 50 |     const timestamp = new Date().toISOString();
 51 |     console.log(`[${timestamp}] 🚀 Step ${step}/${total}: ${msg}`);
 52 |   },
 53 |   separator: () => {
 54 |     console.log('═'.repeat(80));
 55 |   },
 56 |   subseparator: () => {
 57 |     console.log('─'.repeat(60));
 58 |   }
 59 | };
 60 | 
 61 | // Performance timing utility
 62 | class Timer {
 63 |   constructor(name) {
 64 |     this.name = name;
 65 |     this.start = Date.now();
 66 |     log.debug(`Timer started: ${name}`);
 67 |   }
 68 |   
 69 |   lap(description) {
 70 |     const elapsed = Date.now() - this.start;
 71 |     log.debug(`Timer ${this.name} - ${description}: ${elapsed}ms`);
 72 |     return elapsed;
 73 |   }
 74 |   
 75 |   end(description = 'completed') {
 76 |     const elapsed = Date.now() - this.start;
 77 |     log.info(`Timer ${this.name} ${description} in ${elapsed}ms`);
 78 |     return elapsed;
 79 |   }
 80 | }
 81 | 
 82 | // System information logging
 83 | function logSystemInfo() {
 84 |   log.separator();
 85 |   log.info('📋 System Information');
 86 |   log.subseparator();
 87 |   
 88 |   const systemInfo = {
 89 |     platform: os.platform(),
 90 |     arch: os.arch(),
 91 |     nodeVersion: process.version,
 92 |     workingDirectory: process.cwd(),
 93 |     scriptPath: fileURLToPath(import.meta.url),
 94 |     arguments: process.argv.slice(2),
 95 |     environment: process.env.NODE_ENV || 'development',
 96 |     timestamp: new Date().toISOString()
 97 |   };
 98 |   
 99 |   Object.entries(systemInfo).forEach(([key, value]) => {
100 |     log.info(`  ${key}: ${value}`);
101 |   });
102 |   
103 |   log.subseparator();
104 | }
105 | 
106 | // Environment validation with detailed logging
107 | function validateEnvironment() {
108 |   const timer = new Timer('Environment Validation');
109 |   log.step(1, 8, 'Validating environment configuration');
110 |   
111 |   const envVars = {
112 |     'GOOGLE_APP_SCRIPT_API_CLIENT_ID': process.env.GOOGLE_APP_SCRIPT_API_CLIENT_ID,
113 |     'GOOGLE_APP_SCRIPT_API_CLIENT_SECRET': process.env.GOOGLE_APP_SCRIPT_API_CLIENT_SECRET,
114 |     'GOOGLE_APP_SCRIPT_API_REDIRECT_URI': process.env.GOOGLE_APP_SCRIPT_API_REDIRECT_URI
115 |   };
116 |   
117 |   log.debug('Environment variables loaded:', {
118 |     hasClientId: !!envVars.GOOGLE_APP_SCRIPT_API_CLIENT_ID,
119 |     hasClientSecret: !!envVars.GOOGLE_APP_SCRIPT_API_CLIENT_SECRET,
120 |     hasRedirectUri: !!envVars.GOOGLE_APP_SCRIPT_API_REDIRECT_URI,
121 |     clientIdLength: envVars.GOOGLE_APP_SCRIPT_API_CLIENT_ID?.length || 0,
122 |     clientSecretLength: envVars.GOOGLE_APP_SCRIPT_API_CLIENT_SECRET?.length || 0
123 |   });
124 |   
125 |   const validation = {
126 |     hasClientId: !!envVars.GOOGLE_APP_SCRIPT_API_CLIENT_ID && 
127 |                  envVars.GOOGLE_APP_SCRIPT_API_CLIENT_ID !== 'your_client_id_here',
128 |     hasClientSecret: !!envVars.GOOGLE_APP_SCRIPT_API_CLIENT_SECRET && 
129 |                      envVars.GOOGLE_APP_SCRIPT_API_CLIENT_SECRET !== 'your_client_secret_here',
130 |     hasRedirectUri: !!envVars.GOOGLE_APP_SCRIPT_API_REDIRECT_URI
131 |   };
132 |   
133 |   log.info('Environment validation results:', validation);
134 |   
135 |   if (!validation.hasClientId) {
136 |     log.error('Missing or invalid CLIENT_ID in environment');
137 |     throw new Error('GOOGLE_APP_SCRIPT_API_CLIENT_ID is required');
138 |   }
139 |   
140 |   if (!validation.hasClientSecret) {
141 |     log.error('Missing or invalid CLIENT_SECRET in environment');
142 |     throw new Error('GOOGLE_APP_SCRIPT_API_CLIENT_SECRET is required');
143 |   }
144 |   
145 |   if (!validation.hasRedirectUri) {
146 |     log.warn('No REDIRECT_URI specified, using default');
147 |   }
148 |   
149 |   timer.end();
150 |   log.success('Environment validation completed successfully');
151 |   return envVars;
152 | }
153 | 
154 | // File system operations with detailed logging
155 | function validateEnvFile() {
156 |   const timer = new Timer('Env File Validation');
157 |   log.step(2, 8, 'Validating .env file');
158 |   
159 |   const envPath = '.env';
160 |   let envContent = '';
161 |   
162 |   try {
163 |     log.debug(`Reading .env file from: ${envPath}`);
164 |     envContent = readFileSync(envPath, 'utf8');
165 |     const lines = envContent.split('\n');
166 |     const nonEmptyLines = lines.filter(line => line.trim() && !line.trim().startsWith('#'));
167 |     
168 |     log.success('Successfully read .env file', {
169 |       totalLines: lines.length,
170 |       nonEmptyLines: nonEmptyLines.length,
171 |       fileSize: envContent.length
172 |     });
173 |     
174 |     // Analyze .env file content
175 |     const envAnalysis = {
176 |       hasClientId: envContent.includes('GOOGLE_APP_SCRIPT_API_CLIENT_ID='),
177 |       hasClientSecret: envContent.includes('GOOGLE_APP_SCRIPT_API_CLIENT_SECRET='),
178 |       hasRedirectUri: envContent.includes('GOOGLE_APP_SCRIPT_API_REDIRECT_URI='),
179 |       hasComments: envContent.includes('#'),
180 |       hasPlaceholders: envContent.includes('your_client_id_here') || envContent.includes('your_client_secret_here')
181 |     };
182 |     
183 |     log.debug('Env file analysis:', envAnalysis);
184 |     
185 |     if (envAnalysis.hasPlaceholders) {
186 |       log.warn('Found placeholder values in .env file - these need to be replaced with actual credentials');
187 |     }
188 |     
189 |   } catch (error) {
190 |     log.error('Failed to read .env file', error);
191 |     log.info('Expected .env file location:', process.cwd() + '/.env');
192 |     log.info('Please create .env file with required credentials');
193 |     throw error;
194 |   }
195 |   
196 |   timer.end();
197 |   return envContent;
198 | }
199 | 
200 | // Token management with detailed logging
201 | function analyzeExistingTokens(tokenManager) {
202 |   const timer = new Timer('Token Analysis');
203 |   log.step(3, 8, 'Analyzing existing tokens');
204 |   
205 |   try {
206 |     const tokenInfo = tokenManager.getTokenInfo();
207 |     
208 |     log.debug('Token analysis started');
209 |     log.info('Token information retrieved:', {
210 |       hasTokens: tokenInfo.hasTokens,
211 |       location: tokenInfo.location,
212 |       savedAt: tokenInfo.savedAt,
213 |       expiresAt: tokenInfo.expiresAt,
214 |       status: tokenInfo.status,
215 |       isExpired: tokenInfo.isExpired
216 |     });
217 |     
218 |     if (tokenInfo.hasTokens) {
219 |       log.success('Found existing tokens');
220 |       
221 |       if (tokenInfo.isExpired) {
222 |         log.warn('Existing tokens are expired');
223 |       } else {
224 |         log.success('Existing tokens are still valid');
225 |       }
226 |         // Additional token validation
227 |       try {
228 |         const tokenData = tokenManager.loadTokens();
229 |         if (tokenData) {
230 |           log.debug('Token data structure validation:', {
231 |             hasAccessToken: !!tokenData.access_token,
232 |             hasRefreshToken: !!tokenData.refresh_token,
233 |             hasTokenType: !!tokenData.token_type,
234 |             hasScope: !!tokenData.scope,
235 |             expiresIn: tokenData.expires_in
236 |           });
237 |         }
238 |       } catch (tokenError) {
239 |         log.warn('Could not validate token data structure', tokenError);
240 |       }
241 |     } else {
242 |       log.info('No existing tokens found');
243 |     }
244 |     
245 |     timer.end();
246 |     return tokenInfo;
247 |     
248 |   } catch (error) {
249 |     log.error('Failed to analyze existing tokens', error);
250 |     timer.end();
251 |     throw error;
252 |   }
253 | }
254 | 
255 | // OAuth flow with enhanced logging
256 | async function executeOAuthFlow() {
257 |   const timer = new Timer('OAuth Flow');
258 |   log.step(4, 8, 'Starting OAuth authorization flow');
259 |   
260 |   try {
261 |     log.info('Initiating manual OAuth flow');
262 |     log.debug('OAuth flow configuration:', {
263 |       clientId: process.env.GOOGLE_APP_SCRIPT_API_CLIENT_ID?.substring(0, 10) + '...',
264 |       redirectUri: process.env.GOOGLE_APP_SCRIPT_API_REDIRECT_URI,
265 |       scope: 'https://www.googleapis.com/auth/script.projects'
266 |     });
267 |     
268 |     log.info('🌐 Opening browser for OAuth authorization...');
269 |     log.info('📱 Please complete the authorization in your browser');
270 |     log.info('⏳ Waiting for OAuth callback...');
271 |     
272 |     const tokens = await manualOAuthFlow();
273 |     
274 |     log.debug('OAuth flow completed, analyzing response');
275 |     const tokenAnalysis = {
276 |       hasAccessToken: !!tokens.access_token,
277 |       hasRefreshToken: !!tokens.refresh_token,
278 |       hasTokenType: !!tokens.token_type,
279 |       hasScope: !!tokens.scope,
280 |       hasExpiresIn: !!tokens.expires_in,
281 |       accessTokenLength: tokens.access_token?.length || 0,
282 |       refreshTokenLength: tokens.refresh_token?.length || 0,
283 |       tokenType: tokens.token_type,
284 |       scope: tokens.scope,
285 |       expiresIn: tokens.expires_in
286 |     };
287 |     
288 |     log.success('OAuth tokens received', tokenAnalysis);
289 |     
290 |     if (!tokens.refresh_token) {
291 |       log.error('No refresh token received - this is required for long-term access');
292 |       throw new Error('Refresh token missing from OAuth response');
293 |     }
294 |     
295 |     timer.end();
296 |     return tokens;
297 |     
298 |   } catch (error) {
299 |     log.error('OAuth flow failed', error);
300 |     timer.end();
301 |     throw error;
302 |   }
303 | }
304 | 
305 | // Token storage with detailed logging
306 | function saveTokensSecurely(tokenManager, tokens) {
307 |   const timer = new Timer('Token Storage');
308 |   log.step(5, 8, 'Saving tokens securely');
309 |   
310 |   try {
311 |     log.debug('Preparing to save tokens');
312 |     log.info('Token storage location:', tokenManager.getTokenInfo().location);
313 |     
314 |     // Pre-save validation
315 |     const preValidation = {
316 |       hasAccessToken: !!tokens.access_token,
317 |       hasRefreshToken: !!tokens.refresh_token,
318 |       tokenManagerReady: !!tokenManager
319 |     };
320 |     
321 |     log.debug('Pre-save validation:', preValidation);
322 |     
323 |     if (!preValidation.hasAccessToken || !preValidation.hasRefreshToken) {
324 |       throw new Error('Invalid token data - missing required tokens');
325 |     }
326 |     
327 |     log.info('💾 Writing tokens to secure storage...');
328 |     tokenManager.saveTokens(tokens);
329 |     
330 |     // Post-save verification
331 |     const verification = tokenManager.getTokenInfo();
332 |     log.success('Tokens saved successfully', {
333 |       location: verification.location,
334 |       hasTokens: verification.hasTokens,
335 |       savedAt: verification.savedAt,
336 |       status: verification.status
337 |     });
338 |       // Additional verification - try to read back the tokens
339 |     try {
340 |       const savedTokens = tokenManager.loadTokens();
341 |       const verificationCheck = {
342 |         canReadBack: !!savedTokens,
343 |         accessTokenMatches: savedTokens?.access_token === tokens.access_token,
344 |         refreshTokenMatches: savedTokens?.refresh_token === tokens.refresh_token
345 |       };
346 |       
347 |       log.debug('Token verification check:', verificationCheck);
348 |       
349 |       if (!verificationCheck.canReadBack) {
350 |         throw new Error('Cannot read back saved tokens');
351 |       }
352 |       
353 |       if (!verificationCheck.accessTokenMatches || !verificationCheck.refreshTokenMatches) {
354 |         log.warn('Saved tokens do not match original tokens');
355 |       } else {
356 |         log.success('Token integrity verified');
357 |       }
358 |       
359 |     } catch (verificationError) {
360 |       log.warn('Could not verify saved tokens', verificationError);
361 |     }
362 |     
363 |     timer.end();
364 |     return verification;
365 |     
366 |   } catch (error) {
367 |     log.error('Failed to save tokens securely', error);
368 |     timer.end();
369 |     throw error;
370 |   }
371 | }
372 | 
373 | // Main setup function with comprehensive logging
374 | async function setupOAuth() {
375 |   const mainTimer = new Timer('Complete OAuth Setup');
376 |   
377 |   log.separator();
378 |   log.info('🔐 Google Apps Script API OAuth Setup - Enhanced Logging Version');
379 |   log.separator();
380 |   
381 |   // Log system information
382 |   logSystemInfo();
383 |   
384 |   try {
385 |     log.info('📋 Starting OAuth setup process...');
386 |     log.info('📝 This script will guide you through OAuth authentication setup');
387 |     
388 |     // Initialize token manager
389 |     log.step(0, 8, 'Initializing token manager');
390 |     const tokenManager = new TokenManager();
391 |     log.success('Token manager initialized');
392 |     
393 |     // Handle command line arguments
394 |     const args = process.argv.slice(2);
395 |     log.debug('Command line arguments:', args);
396 |     
397 |     // Handle info command
398 |     if (args.includes('--info')) {
399 |       log.info('📊 Information mode requested');
400 |       const tokenInfo = analyzeExistingTokens(tokenManager);
401 |       
402 |       console.log('\n🔍 Token Information Summary:');
403 |       console.log('═'.repeat(40));
404 |       
405 |       if (tokenInfo.hasTokens) {
406 |         console.log('✅ Status: Tokens found');
407 |         console.log(`📁 Location: ${tokenInfo.location}`);
408 |         console.log(`💾 Saved: ${tokenInfo.savedAt}`);
409 |         console.log(`⏰ Expires: ${tokenInfo.expiresAt}`);
410 |         console.log(`📊 Status: ${tokenInfo.status}`);
411 |         console.log(`🔐 Scope: ${tokenInfo.scope || 'Not specified'}`);
412 |       } else {
413 |         console.log('❌ Status: No tokens found');
414 |         console.log(`📁 Expected location: ${tokenInfo.location}`);
415 |         console.log('\n💡 Run "node oauth-setup.js" to set up OAuth tokens');
416 |       }
417 |       
418 |       mainTimer.end('info command completed');
419 |       process.exit(0);
420 |     }
421 |     
422 |     // Handle clear command
423 |     if (args.includes('--clear')) {
424 |       log.info('🗑️ Clear tokens mode requested');
425 |       const tokenInfo = analyzeExistingTokens(tokenManager);
426 |       
427 |       if (tokenInfo.hasTokens) {
428 |         log.info('Clearing existing tokens...');
429 |         tokenManager.clearTokens();
430 |         log.success('Tokens cleared successfully');
431 |       } else {
432 |         log.info('No tokens found to clear');
433 |       }
434 |       
435 |       mainTimer.end('clear command completed');
436 |       process.exit(0);
437 |     }
438 |     
439 |     // Validate environment
440 |     const envVars = validateEnvironment();
441 |     const envContent = validateEnvFile();
442 |     
443 |     // Analyze existing tokens
444 |     const tokenInfo = analyzeExistingTokens(tokenManager);
445 |     
446 |     // Check if we should proceed with OAuth flow
447 |     if (tokenInfo.hasTokens && !tokenInfo.isExpired && !args.includes('--force')) {
448 |       log.success('Valid tokens already exist');
449 |       console.log('\n✅ You already have valid OAuth tokens stored.');
450 |       console.log('💡 To force new token generation: node oauth-setup.js --force');
451 |       console.log('🗑️ To clear existing tokens: node oauth-setup.js --clear');
452 |       console.log('📊 To view token info: node oauth-setup.js --info');
453 |       
454 |       mainTimer.end('setup skipped - valid tokens exist');
455 |       process.exit(0);
456 |     }
457 |     
458 |     if (tokenInfo.hasTokens && args.includes('--force')) {
459 |       log.warn('Force mode enabled - will replace existing tokens');
460 |     }
461 |     
462 |     // Execute OAuth flow
463 |     const tokens = await executeOAuthFlow();
464 |     
465 |     // Save tokens securely
466 |     const saveResult = saveTokensSecurely(tokenManager, tokens);
467 |     
468 |     // Final verification and success message
469 |     log.step(6, 8, 'Performing final verification');
470 |     const finalVerification = analyzeExistingTokens(tokenManager);
471 |     
472 |     if (finalVerification.hasTokens && !finalVerification.isExpired) {
473 |       log.success('Final verification passed - OAuth setup completed successfully');
474 |     } else {
475 |       log.error('Final verification failed');
476 |       throw new Error('Setup completed but final verification failed');
477 |     }
478 |     
479 |     // Success summary
480 |     log.step(7, 8, 'Generating setup summary');
481 |     log.separator();
482 |     log.success('🎉 OAuth Setup Completed Successfully!');
483 |     log.subseparator();
484 |     
485 |     const summary = {
486 |       tokenLocation: saveResult.location,
487 |       setupDuration: mainTimer.end('setup completed'),
488 |       tokenStatus: finalVerification.status,
489 |       nextSteps: [
490 |         'Test your setup: node test-token-management.js',
491 |         'Run MCP server: node mcpServer.js',
492 |         'Check token info: node oauth-setup.js --info'
493 |       ]
494 |     };
495 |     
496 |     log.info('Setup Summary:', summary);
497 |     
498 |     console.log('\n🎉 Setup Complete! Next Steps:');
499 |     summary.nextSteps.forEach((step, index) => {
500 |       console.log(`   ${index + 1}. ${step}`);
501 |     });
502 |     
503 |     log.step(8, 8, 'OAuth setup process completed');
504 |     
505 |   } catch (error) {
506 |     log.error('OAuth setup failed', error);
507 |     
508 |     console.log('\n🔧 Troubleshooting Guide:');
509 |     console.log('════════════════════════');
510 |     console.log('1. 🌐 Check your internet connection');
511 |     console.log('2. 🔑 Verify CLIENT_ID and CLIENT_SECRET are correct');
512 |     console.log('3. 🔗 Ensure redirect URI is registered in Google Cloud Console');
513 |     console.log('4. 🔌 Confirm Google Apps Script API is enabled');
514 |     console.log('5. 🔄 Try revoking and re-creating OAuth credentials');
515 |     console.log('6. 🐛 Enable debug mode: node oauth-setup.js --debug');
516 |     console.log('\n📖 For detailed instructions, see OAUTH_SETUP.md');
517 |     
518 |     mainTimer.end('setup failed');
519 |     process.exit(1);
520 |   }
521 | }
522 | 
523 | // Error handling and execution
524 | setupOAuth().catch((error) => {
525 |   log.error('💥 Unexpected error during setup', error);
526 |   console.error('\n🚨 Critical Error Details:');
527 |   console.error('Message:', error.message);
528 |   if (error.stack) {
529 |     console.error('Stack:', error.stack);
530 |   }
531 |   process.exit(1);
532 | });
```
Page 3/3FirstPrevNextLast