11// Guided setup wizard for google-tools-mcp.
2- // Opens the right Google Cloud Console URLs and saves credentials.
3- import * as readline from 'readline' ;
2+ // Rich terminal UI using @clack /prompts.
3+ import * as p from '@clack/prompts' ;
4+ import chalk from 'chalk' ;
45import * as fs from 'fs/promises' ;
56import * as path from 'path' ;
67import * as os from 'os' ;
@@ -44,10 +45,6 @@ function openBrowser(url) {
4445 exec ( cmd , ( ) => { } ) ;
4546}
4647
47- function prompt ( rl , question ) {
48- return new Promise ( ( resolve ) => rl . question ( question , resolve ) ) ;
49- }
50-
5148function getConfigDir ( ) {
5249 const xdg = process . env . XDG_CONFIG_HOME ;
5350 const base = xdg || path . join ( os . homedir ( ) , '.config' ) ;
@@ -74,106 +71,162 @@ function runCommand(cmd) {
7471 } ) ;
7572}
7673
74+ function cancelled ( ) {
75+ p . cancel ( 'Setup cancelled.' ) ;
76+ process . exit ( 0 ) ;
77+ }
78+
7779// ---------------------------------------------------------------------------
7880// Setup flow
7981// ---------------------------------------------------------------------------
8082export async function runSetup ( ) {
81- const rl = readline . createInterface ( {
82- input : process . stdin ,
83- output : process . stdout ,
83+ console . clear ( ) ;
84+
85+ p . intro ( chalk . bgCyan . bold . white ( ' google-tools-mcp setup ' ) ) ;
86+
87+ // ── Step 1: Enable APIs ──────────────────────────────────────────────
88+ p . log . step ( chalk . cyan . bold ( 'Step 1' ) + chalk . dim ( ' · ' ) + 'Enable Google APIs' ) ;
89+ p . log . message ( [
90+ 'This will open Google Cloud Console to enable all required APIs.' ,
91+ chalk . dim ( 'If you don\'t have a project yet, it will ask you to create one.' ) ,
92+ '' ,
93+ chalk . dim ( 'APIs: ' ) + APIS . map ( a => chalk . yellow ( a . replace ( '.googleapis.com' , '' ) ) ) . join ( chalk . dim ( ', ' ) ) ,
94+ ] . join ( '\n' ) ) ;
95+
96+ const ready1 = await p . confirm ( {
97+ message : 'Ready? This will open your browser.' ,
98+ active : 'open browser' ,
99+ inactive : 'not yet' ,
84100 } ) ;
85-
86- console . log ( '\n🔧 google-tools-mcp setup\n' ) ;
87-
88- // Step 1: Enable APIs
89- console . log ( 'Step 1: Enable Google APIs' ) ;
90- console . log ( '─────────────────────────' ) ;
91- console . log ( 'Opening Google Cloud Console to enable all required APIs.' ) ;
92- console . log ( 'If you don\'t have a project yet, it will ask you to create one.\n' ) ;
101+ if ( p . isCancel ( ready1 ) ) cancelled ( ) ;
93102 openBrowser ( ENABLE_APIS_URL ) ;
94- await prompt ( rl , 'Press Enter when done...' ) ;
95-
96- // Step 2: OAuth consent screen
97- console . log ( '\nStep 2: Configure OAuth consent screen' ) ;
98- console . log ( '──────────────────────────────────────' ) ;
99- console . log ( 'Opening the OAuth consent screen configuration.' ) ;
100- console . log ( ' • Choose "External" for the user type' ) ;
101- console . log ( ' • Fill in the app name (anything is fine, e.g. "MCP")' ) ;
102- console . log ( ' • Add your email as a test user\n' ) ;
103+
104+ const step1 = await p . confirm ( {
105+ message : 'Done enabling APIs?' ,
106+ active : 'yes, continue' ,
107+ inactive : 'not yet' ,
108+ } ) ;
109+ if ( p . isCancel ( step1 ) ) cancelled ( ) ;
110+
111+ // ── Step 2: OAuth consent screen ─────────────────────────────────────
112+ p . log . step ( chalk . cyan . bold ( 'Step 2' ) + chalk . dim ( ' · ' ) + 'Configure OAuth consent screen' ) ;
113+ p . log . message ( [
114+ `${ chalk . white ( '›' ) } Choose ${ chalk . bold ( '"External"' ) } for the user type` ,
115+ `${ chalk . white ( '›' ) } Fill in the app name ${ chalk . dim ( '(anything works, e.g. "MCP")' ) } ` ,
116+ `${ chalk . white ( '›' ) } Add your email as a ${ chalk . bold ( 'test user' ) } ` ,
117+ ] . join ( '\n' ) ) ;
118+
119+ const ready2 = await p . confirm ( {
120+ message : 'Ready? This will open your browser.' ,
121+ active : 'open browser' ,
122+ inactive : 'not yet' ,
123+ } ) ;
124+ if ( p . isCancel ( ready2 ) ) cancelled ( ) ;
103125 openBrowser ( CONSENT_SCREEN_URL ) ;
104- await prompt ( rl , 'Press Enter when done...' ) ;
105-
106- // Step 3: Create OAuth credentials
107- console . log ( '\nStep 3: Create OAuth Client ID' ) ;
108- console . log ( '──────────────────────────────' ) ;
109- console . log ( 'Opening the credentials page.' ) ;
110- console . log ( ' • Select "Desktop application" as the type' ) ;
111- console . log ( ' • Click Create, then copy the Client ID and Client Secret\n' ) ;
126+
127+ const step2 = await p . confirm ( {
128+ message : 'Done configuring consent screen?' ,
129+ active : 'yes, continue' ,
130+ inactive : 'not yet' ,
131+ } ) ;
132+ if ( p . isCancel ( step2 ) ) cancelled ( ) ;
133+
134+ // ── Step 3: Create OAuth credentials ─────────────────────────────────
135+ p . log . step ( chalk . cyan . bold ( 'Step 3' ) + chalk . dim ( ' · ' ) + 'Create OAuth Client ID' ) ;
136+ p . log . message ( [
137+ `${ chalk . white ( '›' ) } Select ${ chalk . bold ( '"Desktop application"' ) } as the type` ,
138+ `${ chalk . white ( '›' ) } Click ${ chalk . bold ( 'Create' ) } ` ,
139+ `${ chalk . white ( '›' ) } Copy the ${ chalk . bold ( 'Client ID' ) } and ${ chalk . bold ( 'Client Secret' ) } ` ,
140+ ] . join ( '\n' ) ) ;
141+
142+ const ready3 = await p . confirm ( {
143+ message : 'Ready? This will open your browser.' ,
144+ active : 'open browser' ,
145+ inactive : 'not yet' ,
146+ } ) ;
147+ if ( p . isCancel ( ready3 ) ) cancelled ( ) ;
112148 openBrowser ( CREATE_CREDENTIALS_URL ) ;
113- await prompt ( rl , 'Press Enter when you have your Client ID and Secret...' ) ;
114149
115- // Step 4: Collect credentials
116- console . log ( '' ) ;
117- const clientId = ( await prompt ( rl , 'Client ID: ' ) ) . trim ( ) ;
118- const clientSecret = ( await prompt ( rl , 'Client Secret: ' ) ) . trim ( ) ;
150+ const credentials = await p . group ( {
151+ clientId : ( ) => p . text ( {
152+ message : 'Client ID' ,
153+ placeholder : 'xxxx.apps.googleusercontent.com' ,
154+ validate : ( v ) => {
155+ if ( ! v ?. trim ( ) ) return 'Client ID is required' ;
156+ } ,
157+ } ) ,
158+ clientSecret : ( ) => p . text ( {
159+ message : 'Client Secret' ,
160+ placeholder : 'GOCSPX-xxxx' ,
161+ validate : ( v ) => {
162+ if ( ! v ?. trim ( ) ) return 'Client Secret is required' ;
163+ } ,
164+ } ) ,
165+ } ) ;
166+ if ( p . isCancel ( credentials ) ) cancelled ( ) ;
119167
120- if ( ! clientId || ! clientSecret ) {
121- rl . close ( ) ;
122- throw new Error ( 'Client ID and Client Secret are required.' ) ;
123- }
168+ const clientId = credentials . clientId . trim ( ) ;
169+ const clientSecret = credentials . clientSecret . trim ( ) ;
124170
125- // Save to config dir
171+ // ── Save credentials ─────────────────────────────────────────────────
126172 const configDir = getConfigDir ( ) ;
127173 await fs . mkdir ( configDir , { recursive : true } ) ;
128174 const envPath = path . join ( configDir , '.env' ) ;
129175 const envContent = `GOOGLE_CLIENT_ID=${ clientId } \nGOOGLE_CLIENT_SECRET=${ clientSecret } \n` ;
130176 await fs . writeFile ( envPath , envContent ) ;
131177 const displayPath = envPath . replace ( os . homedir ( ) , '~' ) ;
132- console . log ( `\nCredentials saved to ${ displayPath } `) ;
178+ p . log . success ( `Credentials saved to ${ chalk . dim ( displayPath ) } `) ;
133179
134- // Step 5: Run OAuth flow
135- console . log ( '\nStep 4: Authenticate with Google' ) ;
136- console . log ( '────────────────────────────────' ) ;
137- console . log ( 'Opening browser for OAuth consent...\n' ) ;
180+ // ── Step 4: Authenticate ─────────────────────────────────────────────
181+ p . log . step ( chalk . cyan . bold ( 'Step 4' ) + chalk . dim ( ' · ' ) + 'Authenticate with Google' ) ;
182+ p . log . message ( 'Opening browser for OAuth consent...' ) ;
138183
139- // Set env vars so auth picks them up immediately
140184 process . env . GOOGLE_CLIENT_ID = clientId ;
141185 process . env . GOOGLE_CLIENT_SECRET = clientSecret ;
142186
143187 const { runAuthFlow } = await import ( './auth.js' ) ;
144188 await runAuthFlow ( ) ;
145189
146- // Step 6: Install into MCP client
147- console . log ( '\nStep 5: Install' ) ;
148- console . log ( '───────────────' ) ;
190+ p . log . success ( 'Authenticated with Google!' ) ;
191+
192+ // ── Step 5: Install ──────────────────────────────────────────────────
193+ p . log . step ( chalk . cyan . bold ( 'Step 5' ) + chalk . dim ( ' · ' ) + 'Install MCP server' ) ;
149194
150195 const hasClaude = hasCli ( 'claude' ) ;
151196 if ( hasClaude ) {
152- const answer = ( await prompt ( rl , 'Add to Claude Code as a user-scope MCP server? (Y/n) ' ) ) . trim ( ) . toLowerCase ( ) ;
153- if ( answer === '' || answer === 'y' || answer === 'yes' ) {
154- console . log ( 'Running: claude mcp add -s user google -- npx -y google-tools-mcp' ) ;
197+ const install = await p . confirm ( {
198+ message : 'Add to Claude Code as a user-scope MCP server?' ,
199+ active : 'yes' ,
200+ inactive : 'no' ,
201+ initialValue : true ,
202+ } ) ;
203+ if ( p . isCancel ( install ) ) cancelled ( ) ;
204+
205+ if ( install ) {
206+ const s = p . spinner ( ) ;
207+ s . start ( 'Adding to Claude Code...' ) ;
155208 try {
156209 await runCommand ( 'claude mcp add -s user google -- npx -y google-tools-mcp' ) ;
157- console . log ( 'Added to Claude Code!' ) ;
210+ s . stop ( 'Added to Claude Code!' ) ;
158211 } catch ( err ) {
159- console . error ( 'Failed to add:' , err . message ) ;
160- console . log ( 'You can add it manually:' ) ;
161- console . log ( ' claude mcp add -s user google -- npx -y google-tools-mcp') ;
212+ s . stop ( 'Failed to add automatically' ) ;
213+ p . log . warn ( `Error: ${ err . message } ` ) ;
214+ p . log . message ( `Run manually:\n ${ chalk . cyan ( ' claude mcp add -s user google -- npx -y google-tools-mcp') } ` ) ;
162215 }
163216 } else {
164- console . log ( '\nTo add it later:' ) ;
165- console . log ( ' claude mcp add -s user google -- npx -y google-tools-mcp' ) ;
217+ p . log . message ( `To add later:\n${ chalk . cyan ( 'claude mcp add -s user google -- npx -y google-tools-mcp' ) } ` ) ;
166218 }
167219 } else {
168- console . log ( 'Add google-tools-mcp to your MCP client config:' ) ;
169- console . log ( '' ) ;
170- console . log ( ' Claude Code:' ) ;
171- console . log ( ' claude mcp add -s user google -- npx -y google-tools-mcp' ) ;
172- console . log ( '' ) ;
173- console . log ( ' Other clients (.mcp.json / claude_desktop_config.json):' ) ;
174- console . log ( ' { "mcpServers": { "google": { "command": "npx", "args": ["-y", "google-tools-mcp"] } } }' ) ;
220+ p . log . message ( [
221+ 'Add to your MCP client:' ,
222+ '' ,
223+ chalk . dim ( 'Claude Code:' ) ,
224+ chalk . cyan ( ' claude mcp add -s user google -- npx -y google-tools-mcp' ) ,
225+ '' ,
226+ chalk . dim ( 'Other clients' ) + chalk . dim ( ' (.mcp.json):' ) ,
227+ chalk . cyan ( ' { "mcpServers": { "google": { "command": "npx", "args": ["-y", "google-tools-mcp"] } } }' ) ,
228+ ] . join ( '\n' ) ) ;
175229 }
176230
177- rl . close ( ) ;
178- console . log ( '\n✅ Setup complete! You\'re ready to use google-tools-mcp.\n' ) ;
231+ p . outro ( chalk . green . bold ( 'Setup complete!' ) + chalk . dim ( ' You\'re ready to use google-tools-mcp.' ) ) ;
179232}
0 commit comments