@_davideast/stitch-mcp 0.3.2 → 0.5.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.
Files changed (214) hide show
  1. package/README.md +54 -239
  2. package/dist/chunk-25wakzyb.js +137 -0
  3. package/dist/chunk-25wakzyb.js.map +10 -0
  4. package/dist/chunk-2cetsfw4.js +16531 -0
  5. package/dist/chunk-2cetsfw4.js.map +95 -0
  6. package/dist/chunk-2fcdwvrm.js +54 -0
  7. package/dist/chunk-2fcdwvrm.js.map +10 -0
  8. package/dist/chunk-2k7n0w2x.js +3980 -0
  9. package/dist/chunk-2k7n0w2x.js.map +16 -0
  10. package/dist/chunk-2kyqsrg2.js +403 -0
  11. package/dist/chunk-2kyqsrg2.js.map +10 -0
  12. package/dist/chunk-384jmtpy.js +11057 -0
  13. package/dist/chunk-384jmtpy.js.map +238 -0
  14. package/dist/chunk-3at4pjgn.js +22 -0
  15. package/dist/chunk-3at4pjgn.js.map +9 -0
  16. package/dist/chunk-3sfn889r.js +492 -0
  17. package/dist/chunk-3sfn889r.js.map +13 -0
  18. package/dist/chunk-45wx7tn7.js +94 -0
  19. package/dist/chunk-45wx7tn7.js.map +10 -0
  20. package/dist/chunk-48d17n29.js +10 -0
  21. package/dist/chunk-48d17n29.js.map +9 -0
  22. package/dist/chunk-4jwmvjb4.js +839 -0
  23. package/dist/chunk-4jwmvjb4.js.map +11 -0
  24. package/dist/chunk-4jygt4d6.js +14 -0
  25. package/dist/chunk-4jygt4d6.js.map +10 -0
  26. package/dist/chunk-4vxy1qce.js +68 -0
  27. package/dist/chunk-4vxy1qce.js.map +10 -0
  28. package/dist/chunk-7tx0wn04.js +11 -0
  29. package/dist/chunk-7tx0wn04.js.map +9 -0
  30. package/dist/chunk-7xh1y383.js +45188 -0
  31. package/dist/chunk-7xh1y383.js.map +258 -0
  32. package/dist/chunk-8yrtq2qs.js +18 -0
  33. package/dist/chunk-8yrtq2qs.js.map +9 -0
  34. package/dist/chunk-985f11w6.js +21 -0
  35. package/dist/chunk-985f11w6.js.map +9 -0
  36. package/dist/chunk-9tvppjaf.js +250 -0
  37. package/dist/chunk-9tvppjaf.js.map +14 -0
  38. package/dist/chunk-9wyra8hs.js +32 -0
  39. package/dist/chunk-9wyra8hs.js.map +9 -0
  40. package/dist/chunk-cwkb2wbe.js +733 -0
  41. package/dist/chunk-cwkb2wbe.js.map +16 -0
  42. package/dist/chunk-djhzzcgj.js +362 -0
  43. package/dist/chunk-djhzzcgj.js.map +13 -0
  44. package/dist/chunk-edp6faw2.js +7 -0
  45. package/dist/chunk-edp6faw2.js.map +9 -0
  46. package/dist/chunk-ezmw2j8c.js +14 -0
  47. package/dist/chunk-ezmw2j8c.js.map +9 -0
  48. package/dist/chunk-f2hq6bfv.js +22 -0
  49. package/dist/chunk-f2hq6bfv.js.map +10 -0
  50. package/dist/chunk-fwb4fnkp.js +31 -0
  51. package/dist/chunk-fwb4fnkp.js.map +10 -0
  52. package/dist/chunk-h18jrqed.js +9517 -0
  53. package/dist/chunk-h18jrqed.js.map +99 -0
  54. package/dist/chunk-hb3c6f6a.js +42 -0
  55. package/dist/chunk-hb3c6f6a.js.map +9 -0
  56. package/dist/chunk-jy2d17pr.js +252 -0
  57. package/dist/chunk-jy2d17pr.js.map +11 -0
  58. package/dist/chunk-kbtqrkwh.js +24 -0
  59. package/dist/chunk-kbtqrkwh.js.map +10 -0
  60. package/dist/chunk-knbnsf6s.js +92 -0
  61. package/dist/chunk-knbnsf6s.js.map +10 -0
  62. package/dist/chunk-mv9ssgmx.js +446 -0
  63. package/dist/chunk-mv9ssgmx.js.map +17 -0
  64. package/dist/chunk-nq68kghz.js +1647 -0
  65. package/dist/chunk-nq68kghz.js.map +10 -0
  66. package/dist/chunk-nthabjd9.js +3138 -0
  67. package/dist/chunk-nthabjd9.js.map +34 -0
  68. package/dist/chunk-nxpzt33t.js +278 -0
  69. package/dist/chunk-nxpzt33t.js.map +10 -0
  70. package/dist/chunk-pfyjtfex.js +172 -0
  71. package/dist/chunk-pfyjtfex.js.map +10 -0
  72. package/dist/chunk-q1nd6g0y.js +392 -0
  73. package/dist/chunk-q1nd6g0y.js.map +20 -0
  74. package/dist/chunk-q4js8r0z.js +4708 -0
  75. package/dist/chunk-q4js8r0z.js.map +29 -0
  76. package/dist/chunk-qv44tmn6.js +289 -0
  77. package/dist/chunk-qv44tmn6.js.map +13 -0
  78. package/dist/chunk-r2sg2nxa.js +20 -0
  79. package/dist/chunk-r2sg2nxa.js.map +9 -0
  80. package/dist/chunk-rp8wjzht.js +16959 -0
  81. package/dist/chunk-rp8wjzht.js.map +26 -0
  82. package/dist/chunk-rpxnm86e.js +372 -0
  83. package/dist/chunk-rpxnm86e.js.map +17 -0
  84. package/dist/chunk-sdp429xd.js +914 -0
  85. package/dist/chunk-sdp429xd.js.map +24 -0
  86. package/dist/chunk-t85nbjjb.js +113 -0
  87. package/dist/chunk-t85nbjjb.js.map +10 -0
  88. package/dist/chunk-tz7wnw4s.js +211 -0
  89. package/dist/chunk-tz7wnw4s.js.map +11 -0
  90. package/dist/chunk-v7117ywx.js +1477 -0
  91. package/dist/chunk-v7117ywx.js.map +23 -0
  92. package/dist/chunk-w8q7nsm7.js +2098 -0
  93. package/dist/chunk-w8q7nsm7.js.map +44 -0
  94. package/dist/chunk-wa64nz8b.js +47 -0
  95. package/dist/chunk-wa64nz8b.js.map +10 -0
  96. package/dist/chunk-wz8d5vzb.js +234 -0
  97. package/dist/chunk-wz8d5vzb.js.map +11 -0
  98. package/dist/chunk-ycfxp056.js +677 -0
  99. package/dist/chunk-ycfxp056.js.map +17 -0
  100. package/dist/chunk-z9d2xc83.js +3256 -0
  101. package/dist/chunk-z9d2xc83.js.map +84 -0
  102. package/dist/cli.js +111 -125552
  103. package/dist/cli.js.map +19 -0
  104. package/dist/commands/autoload.d.ts +2 -0
  105. package/dist/commands/doctor/command.d.ts +2 -1
  106. package/dist/commands/doctor/command.js +82 -0
  107. package/dist/commands/doctor/command.js.map +11 -0
  108. package/dist/commands/doctor/spec.d.ts +16 -8
  109. package/dist/commands/init/command.d.ts +2 -1
  110. package/dist/commands/init/command.js +98 -0
  111. package/dist/commands/init/command.js.map +11 -0
  112. package/dist/commands/init/spec.d.ts +29 -9
  113. package/dist/commands/logout/command.d.ts +2 -1
  114. package/dist/commands/logout/command.js +79 -0
  115. package/dist/commands/logout/command.js.map +11 -0
  116. package/dist/commands/logout/spec.d.ts +11 -0
  117. package/dist/commands/proxy/command.d.ts +2 -1
  118. package/dist/commands/proxy/command.js +56 -0
  119. package/dist/commands/proxy/command.js.map +11 -0
  120. package/dist/commands/proxy/spec.d.ts +15 -0
  121. package/dist/commands/screens/command.d.ts +2 -1
  122. package/dist/commands/screens/command.js +61 -0
  123. package/dist/commands/screens/command.js.map +11 -0
  124. package/dist/commands/screens/spec.d.ts +9 -0
  125. package/dist/commands/serve/command.d.ts +2 -1
  126. package/dist/commands/serve/command.js +65 -0
  127. package/dist/commands/serve/command.js.map +11 -0
  128. package/dist/commands/serve/spec.d.ts +9 -0
  129. package/dist/commands/site/command.d.ts +2 -1
  130. package/dist/commands/site/command.js +53 -0
  131. package/dist/commands/site/command.js.map +11 -0
  132. package/dist/commands/site/spec.d.ts +15 -0
  133. package/dist/commands/snapshot/command.d.ts +2 -1
  134. package/dist/commands/snapshot/command.js +59 -0
  135. package/dist/commands/snapshot/command.js.map +11 -0
  136. package/dist/commands/tool/command.d.ts +2 -1
  137. package/dist/commands/tool/command.js +79 -0
  138. package/dist/commands/tool/command.js.map +11 -0
  139. package/dist/commands/tool/handler.d.ts +17 -0
  140. package/dist/commands/tool/spec.d.ts +17 -0
  141. package/dist/commands/tool/steps/ValidateToolStep.d.ts +8 -0
  142. package/dist/commands/tool/virtual-tools/index.d.ts +1 -0
  143. package/dist/commands/tool/virtual-tools/list-tools.d.ts +2 -0
  144. package/dist/commands/view/command.d.ts +2 -1
  145. package/dist/commands/view/command.js +52 -0
  146. package/dist/commands/view/command.js.map +11 -0
  147. package/dist/commands/view/spec.d.ts +24 -0
  148. package/dist/framework/CommandDefinition.d.ts +2 -2
  149. package/dist/index.js +26 -21668
  150. package/dist/index.js.map +9 -0
  151. package/dist/lib/services/site/schemas.d.ts +8 -8
  152. package/dist/services/config/handler.d.ts +12 -0
  153. package/dist/services/config/spec.d.ts +82 -0
  154. package/dist/services/gcloud/auth.d.ts +28 -0
  155. package/dist/services/gcloud/core.d.ts +21 -0
  156. package/dist/services/gcloud/handler.d.ts +6 -18
  157. package/dist/services/gcloud/install.d.ts +23 -0
  158. package/dist/services/gcloud/projects.d.ts +15 -0
  159. package/dist/services/gcloud/spec.d.ts +32 -24
  160. package/dist/services/mcp-client/client.d.ts +0 -5
  161. package/dist/services/mcp-client/spec.d.ts +6 -6
  162. package/dist/services/mcp-config/spec.d.ts +12 -12
  163. package/dist/services/project/spec.d.ts +4 -4
  164. package/dist/services/proxy/spec.d.ts +3 -3
  165. package/dist/services/stitch/api.d.ts +10 -0
  166. package/dist/services/stitch/connection.d.ts +5 -0
  167. package/dist/services/stitch/handler.d.ts +5 -3
  168. package/dist/services/stitch/iam.d.ts +11 -0
  169. package/dist/services/stitch/spec.d.ts +6 -6
  170. package/dist/services/view/spec.d.ts +7 -7
  171. package/dist/src/cli.d.ts +1 -0
  172. package/dist/src/commands/doctor/handler.d.ts +9 -0
  173. package/dist/src/commands/doctor/handler.test.d.ts +1 -0
  174. package/dist/src/commands/doctor/spec.d.ts +130 -0
  175. package/dist/src/commands/doctor/spec.test.d.ts +1 -0
  176. package/dist/src/commands/init/handler.d.ts +17 -0
  177. package/dist/src/commands/init/handler.test.d.ts +1 -0
  178. package/dist/src/commands/init/spec.d.ts +88 -0
  179. package/dist/src/commands/init/spec.test.d.ts +1 -0
  180. package/dist/src/commands/proxy/handler.d.ts +7 -0
  181. package/dist/src/commands/proxy/handler.test.d.ts +1 -0
  182. package/dist/src/index.d.ts +13 -0
  183. package/dist/src/platform/detector.d.ts +29 -0
  184. package/dist/src/platform/paths.d.ts +20 -0
  185. package/dist/src/platform/shell.d.ts +26 -0
  186. package/dist/src/services/gcloud/handler.d.ts +48 -0
  187. package/dist/src/services/gcloud/handler.test.d.ts +1 -0
  188. package/dist/src/services/gcloud/spec.d.ts +405 -0
  189. package/dist/src/services/mcp-config/handler.d.ts +12 -0
  190. package/dist/src/services/mcp-config/handler.test.d.ts +1 -0
  191. package/dist/src/services/mcp-config/spec.d.ts +88 -0
  192. package/dist/src/services/mcp-config/spec.test.d.ts +1 -0
  193. package/dist/src/services/project/handler.d.ts +11 -0
  194. package/dist/src/services/project/handler.test.d.ts +1 -0
  195. package/dist/src/services/project/spec.d.ts +86 -0
  196. package/dist/src/services/project/spec.test.d.ts +1 -0
  197. package/dist/src/services/proxy/handler.d.ts +15 -0
  198. package/dist/src/services/proxy/handler.test.d.ts +1 -0
  199. package/dist/src/services/proxy/spec.d.ts +83 -0
  200. package/dist/src/services/proxy/spec.test.d.ts +1 -0
  201. package/dist/src/services/stitch/handler.d.ts +15 -0
  202. package/dist/src/services/stitch/handler.test.d.ts +1 -0
  203. package/dist/src/services/stitch/spec.d.ts +262 -0
  204. package/dist/src/services/stitch/spec.test.d.ts +1 -0
  205. package/dist/src/ui/spinner.d.ts +11 -0
  206. package/dist/src/ui/theme.d.ts +18 -0
  207. package/dist/src/ui/wizard.d.ts +24 -0
  208. package/dist/tests/mocks/shell.d.ts +2 -0
  209. package/dist/ui/JsonTree.d.ts +1 -1
  210. package/package.json +2 -2
  211. package/dist/commands/registry.d.ts +0 -2
  212. package/dist/ui/copy-behaviors/index.d.ts +0 -8
  213. package/dist/ui/serve-behaviors/index.d.ts +0 -7
  214. /package/dist/ui/navigation-behaviors/{index.d.ts → handler.d.ts} +0 -0
@@ -0,0 +1,1477 @@
1
+ import {
2
+ StitchHandler,
3
+ createSpinner
4
+ } from "./chunk-sdp429xd.js";
5
+ import {
6
+ ConsoleUI,
7
+ promptConfirm,
8
+ promptInput,
9
+ promptSelect
10
+ } from "./chunk-z9d2xc83.js";
11
+ import {
12
+ runSteps
13
+ } from "./chunk-f2hq6bfv.js";
14
+ import {
15
+ GcloudHandler,
16
+ execCommand
17
+ } from "./chunk-nthabjd9.js";
18
+ import {
19
+ icons,
20
+ theme
21
+ } from "./chunk-kbtqrkwh.js";
22
+ import {
23
+ __require
24
+ } from "./chunk-9wyra8hs.js";
25
+
26
+ // src/services/project/handler.ts
27
+ class ProjectHandler {
28
+ gcloudService;
29
+ constructor(gcloudService) {
30
+ this.gcloudService = gcloudService;
31
+ }
32
+ async selectProject(input) {
33
+ try {
34
+ const projectsResult = await this.gcloudService.listProjects({
35
+ limit: input.limit,
36
+ sortBy: "~createTime"
37
+ });
38
+ if (!projectsResult.success) {
39
+ return {
40
+ success: false,
41
+ error: {
42
+ code: "SEARCH_FAILED",
43
+ message: "Failed to fetch projects",
44
+ suggestion: "Ensure you are authenticated and have access to GCP projects",
45
+ recoverable: true
46
+ }
47
+ };
48
+ }
49
+ const projects = projectsResult.data.projects;
50
+ if (projects.length === 0) {
51
+ return {
52
+ success: false,
53
+ error: {
54
+ code: "NO_PROJECTS_FOUND",
55
+ message: "No projects found in your account",
56
+ suggestion: "Create a project at https://console.cloud.google.com",
57
+ recoverable: false
58
+ }
59
+ };
60
+ }
61
+ const choices = [
62
+ ...input.allowSearch ? [{ name: theme.gray("\uD83D\uDD0D Search for a project..."), value: "__SEARCH__" }] : [],
63
+ ...projects.map((p) => ({
64
+ name: `${p.name} ${theme.gray(`(${p.projectId})`)}`,
65
+ value: p.projectId
66
+ }))
67
+ ];
68
+ const selected = await promptSelect("Select a project", choices);
69
+ if (selected === "__SEARCH__") {
70
+ return await this.searchAndSelect();
71
+ }
72
+ const selectedProject = projects.find((p) => p.projectId === selected);
73
+ if (!selectedProject) {
74
+ return {
75
+ success: false,
76
+ error: {
77
+ code: "SELECTION_CANCELLED",
78
+ message: "Project selection failed",
79
+ recoverable: true
80
+ }
81
+ };
82
+ }
83
+ return {
84
+ success: true,
85
+ data: {
86
+ projectId: selectedProject.projectId,
87
+ name: selectedProject.name
88
+ }
89
+ };
90
+ } catch (error) {
91
+ return {
92
+ success: false,
93
+ error: {
94
+ code: "UNKNOWN_ERROR",
95
+ message: error instanceof Error ? error.message : String(error),
96
+ recoverable: false
97
+ }
98
+ };
99
+ }
100
+ }
101
+ async getProjectDetails(input) {
102
+ try {
103
+ const projectResult = await this.gcloudService.listProjects({
104
+ filter: `projectId:${input.projectId}`,
105
+ limit: 1
106
+ });
107
+ if (!projectResult.success) {
108
+ return {
109
+ success: false,
110
+ error: {
111
+ code: "PROJECT_FETCH_FAILED",
112
+ message: `Failed to fetch project details: ${projectResult.error.message}`,
113
+ recoverable: true
114
+ }
115
+ };
116
+ }
117
+ if (projectResult.data.projects.length === 0) {
118
+ return {
119
+ success: false,
120
+ error: {
121
+ code: "PROJECT_NOT_FOUND",
122
+ message: `Project not found: ${input.projectId}`,
123
+ recoverable: true
124
+ }
125
+ };
126
+ }
127
+ const project = projectResult.data.projects[0];
128
+ return {
129
+ success: true,
130
+ data: {
131
+ projectId: project.projectId,
132
+ name: project.name
133
+ }
134
+ };
135
+ } catch (error) {
136
+ return {
137
+ success: false,
138
+ error: {
139
+ code: "UNKNOWN_ERROR",
140
+ message: error instanceof Error ? error.message : String(error),
141
+ recoverable: false
142
+ }
143
+ };
144
+ }
145
+ }
146
+ async searchAndSelect() {
147
+ try {
148
+ const query = await promptInput("Enter project name or ID to search (press Enter)");
149
+ if (!query.trim()) {
150
+ return {
151
+ success: false,
152
+ error: {
153
+ code: "SELECTION_CANCELLED",
154
+ message: "Search cancelled",
155
+ recoverable: true
156
+ }
157
+ };
158
+ }
159
+ const searchResult = await this.gcloudService.listProjects({
160
+ filter: `name:*${query}* OR projectId:*${query}*`,
161
+ limit: 5
162
+ });
163
+ if (!searchResult.success) {
164
+ return {
165
+ success: false,
166
+ error: {
167
+ code: "SEARCH_FAILED",
168
+ message: `Search failed: ${searchResult.error.message}`,
169
+ recoverable: true
170
+ }
171
+ };
172
+ }
173
+ const projects = searchResult.data.projects;
174
+ if (projects.length === 0) {
175
+ const useManual = await promptConfirm(`No projects found matching "${query}". Use "${query}" as project ID?`, false);
176
+ if (useManual) {
177
+ return {
178
+ success: true,
179
+ data: {
180
+ projectId: query,
181
+ name: query
182
+ }
183
+ };
184
+ }
185
+ return {
186
+ success: false,
187
+ error: {
188
+ code: "NO_PROJECTS_FOUND",
189
+ message: `No projects found matching "${query}"`,
190
+ suggestion: "Try a different search term or select from recent projects",
191
+ recoverable: true
192
+ }
193
+ };
194
+ }
195
+ const choices = projects.map((p) => ({
196
+ name: `${p.name} ${theme.gray(`(${p.projectId})`)}`,
197
+ value: p.projectId
198
+ }));
199
+ const selected = await promptSelect(`Search results for "${query}"`, choices);
200
+ const selectedProject = projects.find((p) => p.projectId === selected);
201
+ if (!selectedProject) {
202
+ return {
203
+ success: false,
204
+ error: {
205
+ code: "SELECTION_CANCELLED",
206
+ message: "Selection cancelled",
207
+ recoverable: true
208
+ }
209
+ };
210
+ }
211
+ return {
212
+ success: true,
213
+ data: {
214
+ projectId: selectedProject.projectId,
215
+ name: selectedProject.name
216
+ }
217
+ };
218
+ } catch (error) {
219
+ return {
220
+ success: false,
221
+ error: {
222
+ code: "UNKNOWN_ERROR",
223
+ message: error instanceof Error ? error.message : String(error),
224
+ recoverable: false
225
+ }
226
+ };
227
+ }
228
+ }
229
+ }
230
+
231
+ // src/services/mcp-config/handler.ts
232
+ class McpConfigHandler {
233
+ async generateConfig(input) {
234
+ try {
235
+ const config = input.transport === "http" ? this.generateHttpConfig(input) : this.generateStdioConfig(input);
236
+ const configString = config ? JSON.stringify(config, null, 2) : "";
237
+ const instructions = this.getInstructionsForClient(input.client, configString, input.transport, input.projectId, input.apiKey);
238
+ return {
239
+ success: true,
240
+ data: {
241
+ config: configString,
242
+ instructions
243
+ }
244
+ };
245
+ } catch (error) {
246
+ return {
247
+ success: false,
248
+ error: {
249
+ code: "CONFIG_GENERATION_FAILED",
250
+ message: error instanceof Error ? error.message : String(error),
251
+ recoverable: false
252
+ }
253
+ };
254
+ }
255
+ }
256
+ generateHttpConfig(input) {
257
+ switch (input.client) {
258
+ case "cursor":
259
+ return this.generateCursorConfig(input.projectId, input.apiKey);
260
+ case "antigravity":
261
+ return this.generateAntigravityConfig(input.projectId, input.apiKey);
262
+ case "vscode":
263
+ return this.generateVSCodeConfig(input.projectId, input.apiKey);
264
+ case "claude-code":
265
+ return this.generateClaudeCodeConfig();
266
+ case "gemini-cli":
267
+ return this.generateGeminiCliConfig();
268
+ case "codex":
269
+ return null;
270
+ case "opencode":
271
+ return this.generateOpencodeConfig(input.apiKey);
272
+ }
273
+ }
274
+ generateCursorConfig(projectId, apiKey) {
275
+ const headers = apiKey ? { "X-Goog-Api-Key": apiKey } : {
276
+ Authorization: "Bearer <YOUR_ACCESS_TOKEN>",
277
+ "X-Goog-User-Project": projectId
278
+ };
279
+ return {
280
+ mcpServers: {
281
+ stitch: {
282
+ url: "https://stitch.googleapis.com/mcp",
283
+ headers
284
+ }
285
+ }
286
+ };
287
+ }
288
+ generateAntigravityConfig(projectId, apiKey) {
289
+ const headers = apiKey ? { "X-Goog-Api-Key": apiKey } : {
290
+ Authorization: "Bearer <YOUR_ACCESS_TOKEN>",
291
+ "X-Goog-User-Project": projectId
292
+ };
293
+ return {
294
+ mcpServers: {
295
+ stitch: {
296
+ serverUrl: "https://stitch.googleapis.com/mcp",
297
+ headers
298
+ }
299
+ }
300
+ };
301
+ }
302
+ generateVSCodeConfig(projectId, apiKey) {
303
+ if (apiKey) {
304
+ return {
305
+ servers: {
306
+ stitch: {
307
+ type: "http",
308
+ url: "https://stitch.googleapis.com/mcp",
309
+ headers: {
310
+ Accept: "application/json",
311
+ "X-Goog-Api-Key": apiKey
312
+ }
313
+ }
314
+ }
315
+ };
316
+ }
317
+ return {
318
+ inputs: [
319
+ {
320
+ type: "promptString",
321
+ id: "stitch-access-token",
322
+ description: "Google Cloud Access Token (run: gcloud auth print-access-token)",
323
+ password: true
324
+ }
325
+ ],
326
+ servers: {
327
+ stitch: {
328
+ type: "http",
329
+ url: "https://stitch.googleapis.com/mcp",
330
+ headers: {
331
+ Authorization: "Bearer ${input:stitch-access-token}",
332
+ "X-Goog-User-Project": projectId
333
+ }
334
+ }
335
+ }
336
+ };
337
+ }
338
+ generateClaudeCodeConfig() {
339
+ return null;
340
+ }
341
+ generateGeminiCliConfig() {
342
+ return null;
343
+ }
344
+ generateOpencodeConfig(apiKey) {
345
+ const headers = apiKey ? { "X-Goog-Api-Key": apiKey } : {
346
+ Authorization: "Bearer $STITCH_ACCESS_TOKEN",
347
+ "X-Goog-User-Project": "$GOOGLE_CLOUD_PROJECT"
348
+ };
349
+ return {
350
+ $schema: "https://opencode.ai/config.json",
351
+ mcp: {
352
+ stitch: {
353
+ type: "remote",
354
+ url: "https://stitch.googleapis.com/mcp",
355
+ headers
356
+ }
357
+ }
358
+ };
359
+ }
360
+ generateStdioConfig(input) {
361
+ if (input.client === "claude-code" || input.client === "gemini-cli" || input.client === "codex") {
362
+ return null;
363
+ }
364
+ const env = {};
365
+ if (!input.apiKey) {
366
+ env.STITCH_PROJECT_ID = input.projectId;
367
+ } else {
368
+ env.STITCH_API_KEY = input.apiKey;
369
+ }
370
+ if (input.client === "vscode") {
371
+ return {
372
+ servers: {
373
+ stitch: {
374
+ type: "stdio",
375
+ command: "npx",
376
+ args: ["@_davideast/stitch-mcp", "proxy"],
377
+ env
378
+ }
379
+ }
380
+ };
381
+ }
382
+ if (input.client === "opencode") {
383
+ return {
384
+ $schema: "https://opencode.ai/config.json",
385
+ mcp: {
386
+ stitch: {
387
+ type: "local",
388
+ command: ["npx", "@_davideast/stitch-mcp", "proxy"],
389
+ environment: env
390
+ }
391
+ }
392
+ };
393
+ }
394
+ return {
395
+ mcpServers: {
396
+ stitch: {
397
+ command: "npx",
398
+ args: ["@_davideast/stitch-mcp", "proxy"],
399
+ env
400
+ }
401
+ }
402
+ };
403
+ }
404
+ getInstructionsForClient(client, config, transport, projectId, apiKey) {
405
+ const baseInstructions = `
406
+ ${theme.blue("MCP Configuration Generated")}
407
+
408
+ ${config}
409
+ `;
410
+ const transportNote = transport === "stdio" ? `
411
+ ${theme.yellow("Note:")} This uses the proxy server. Keep it running with:
412
+ npx @_davideast/stitch-mcp proxy
413
+ ` : "";
414
+ const tokenHint = transport === "http" && !apiKey ? `
415
+ ${theme.yellow("To get your access token, run:")}
416
+ ` + ` CLOUDSDK_CONFIG=~/.stitch-mcp/config ~/.stitch-mcp/google-cloud-sdk/bin/gcloud auth print-access-token
417
+ ` + `
418
+ ${theme.yellow("Important:")} Replace ${theme.blue("<YOUR_ACCESS_TOKEN>")} in the config with the token from the command above.
419
+ ` + `Access tokens expire after 1 hour. Consider using ${theme.blue("stdio")} transport for automatic refresh.
420
+ ` : "";
421
+ const vscodeTokenHint = transport === "http" && !apiKey ? `
422
+ ${theme.yellow("To get your access token, run:")}
423
+ ` + ` CLOUDSDK_CONFIG=~/.stitch-mcp/config ~/.stitch-mcp/google-cloud-sdk/bin/gcloud auth print-access-token
424
+ ` + `
425
+ ${theme.yellow("Important:")} When prompted, paste the token from the command above.
426
+ ` + `Access tokens expire after 1 hour. Consider using ${theme.blue("stdio")} transport for automatic refresh.
427
+ ` : "";
428
+ switch (client) {
429
+ case "antigravity":
430
+ if (transport === "stdio") {
431
+ return baseInstructions + transportNote + `
432
+ ${theme.green("Next Steps for Antigravity:")}
433
+ ` + `1. In the Agent Panel, click the three dots in the top right
434
+ ` + `2. Select "MCP Servers" → "Manage MCP Servers"
435
+ ` + `3. Select "View raw config" and add the above configuration
436
+ ` + `4. Restart Antigravity to load the configuration
437
+ `;
438
+ }
439
+ return baseInstructions + tokenHint + `
440
+ ${theme.green("Next Steps for Antigravity:")}
441
+ ` + `1. In the Agent Panel, click the three dots in the top right
442
+ ` + `2. Select "MCP Servers" → "Manage MCP Servers"
443
+ ` + `3. Select "View raw config" and add the above configuration
444
+ ` + `4. Restart Antigravity to load the configuration
445
+ `;
446
+ case "vscode":
447
+ if (transport === "stdio") {
448
+ return baseInstructions + `
449
+ ${theme.green("Next Steps for VSCode:")}
450
+ ` + `1. Open the Command Palette (Ctrl+Shift+P or Cmd+Shift+P)
451
+ ` + `2. Run "MCP: Open User Configuration" or "MCP: Open Workspace Folder Configuration"
452
+ ` + `3. Add the above configuration to the mcp.json file
453
+ ` + `4. VS Code will automatically start the proxy server when needed
454
+ `;
455
+ }
456
+ return baseInstructions + vscodeTokenHint + `
457
+ ${theme.green("Next Steps for VSCode:")}
458
+ ` + `1. Open the Command Palette (Ctrl+Shift+P or Cmd+Shift+P)
459
+ ` + `2. Run "MCP: Open User Configuration" or "MCP: Open Workspace Folder Configuration"
460
+ ` + `3. Add the above configuration to the mcp.json file
461
+ ` + (apiKey ? "" : `4. When prompted, paste the access token from the command above
462
+ `) + `5. Restart VS Code or run "MCP: List Servers" to start the server
463
+ `;
464
+ case "cursor":
465
+ if (transport === "stdio") {
466
+ return baseInstructions + transportNote + `
467
+ ${theme.green("Next Steps for Cursor:")}
468
+ ` + `1. Create a .cursor/mcp.json file in your project root
469
+ ` + `2. Add the above configuration to the file
470
+ ` + `3. Restart Cursor to load the configuration
471
+ `;
472
+ }
473
+ return baseInstructions + tokenHint + `
474
+ ${theme.green("Next Steps for Cursor:")}
475
+ ` + `1. Create a .cursor/mcp.json file in your project root
476
+ ` + `2. Add the above configuration to the file
477
+ ` + `3. Restart Cursor to load the configuration
478
+ `;
479
+ case "claude-code":
480
+ if (transport === "stdio") {
481
+ return transportNote + `
482
+ ${theme.green("Setup Claude Code:")}
483
+
484
+ ` + `Run the following command to add the Stitch MCP server:
485
+
486
+ ` + `${theme.blue("claude mcp add stitch \\")}
487
+ ` + `${theme.blue(" -- npx @_davideast/stitch-mcp proxy")}`;
488
+ } else {
489
+ if (apiKey) {
490
+ return `
491
+ ${theme.green("Setup Claude Code:")}
492
+
493
+ ` + `Run the following command to add the Stitch MCP server:
494
+
495
+ ` + `${theme.blue("claude mcp add stitch \\")}
496
+ ` + `${theme.blue(" --transport http https://stitch.googleapis.com/mcp \\")}
497
+ ` + `${theme.blue(` --header "X-Goog-Api-Key: ${apiKey}" \\`)}
498
+ ` + `${theme.blue(" -s user")}
499
+
500
+ ` + `${theme.yellow("Note:")} -s user saves to $HOME/.claude.json, use -s project for ./.mcp.json
501
+ `;
502
+ }
503
+ return tokenHint + `
504
+ ${theme.green("Setup Claude Code:")}
505
+
506
+ ` + `Run the following command to add the Stitch MCP server:
507
+
508
+ ` + `${theme.blue("claude mcp add stitch \\")}
509
+ ` + `${theme.blue(" --transport http https://stitch.googleapis.com/mcp \\")}
510
+ ` + `${theme.blue(' --header "Authorization: Bearer <YOUR_ACCESS_TOKEN>" \\')}
511
+ ` + `${theme.blue(` --header "X-Goog-User-Project: ${projectId}" \\`)}
512
+ ` + `${theme.blue(" -s user")}
513
+
514
+ ` + `${theme.yellow("Note:")} -s user saves to $HOME/.claude.json, use -s project for ./.mcp.json
515
+ `;
516
+ }
517
+ case "gemini-cli":
518
+ return transportNote + `
519
+ ${theme.green("Setup Gemini CLI:")}
520
+
521
+ ` + `Install the Stitch extension for the Gemini CLI:
522
+
523
+ ` + `${theme.blue("gemini extensions install https://github.com/gemini-cli-extensions/stitch")}
524
+ `;
525
+ case "codex": {
526
+ const isHttp = transport === "http";
527
+ let configBlock;
528
+ if (isHttp) {
529
+ if (apiKey) {
530
+ configBlock = [
531
+ "[mcp_servers.stitch]",
532
+ 'url = "https://stitch.googleapis.com/mcp"',
533
+ "",
534
+ "[mcp_servers.stitch.env_http_headers]",
535
+ `X-Goog-Api-Key = "${apiKey}"`
536
+ ].join(`
537
+ `);
538
+ } else {
539
+ configBlock = [
540
+ "[mcp_servers.stitch]",
541
+ 'url = "https://stitch.googleapis.com/mcp"',
542
+ 'bearer_token_env_var = "STITCH_ACCESS_TOKEN"',
543
+ "",
544
+ "[mcp_servers.stitch.env_http_headers]",
545
+ 'X-Goog-User-Project = "GOOGLE_CLOUD_PROJECT"'
546
+ ].join(`
547
+ `);
548
+ }
549
+ } else {
550
+ if (apiKey) {
551
+ configBlock = [
552
+ "[mcp_servers.stitch]",
553
+ 'command = "npx"',
554
+ 'args = ["@_davideast/stitch-mcp", "proxy"]',
555
+ "",
556
+ "[mcp_servers.stitch.env]",
557
+ `STITCH_API_KEY = "${apiKey}"`
558
+ ].join(`
559
+ `);
560
+ } else {
561
+ configBlock = [
562
+ "[mcp_servers.stitch]",
563
+ 'command = "npx"',
564
+ 'args = ["@_davideast/stitch-mcp", "proxy"]',
565
+ "",
566
+ "[mcp_servers.stitch.env]",
567
+ `STITCH_PROJECT_ID = "${projectId}"`
568
+ ].join(`
569
+ `);
570
+ }
571
+ }
572
+ const note = isHttp && !apiKey ? `${theme.yellow("Note:")} Direct mode requires a valid access token in ${theme.blue("STITCH_ACCESS_TOKEN")} and a project id in ${theme.blue("GOOGLE_CLOUD_PROJECT")}.
573
+ ` : `${theme.yellow("Note:")} Proxy mode handles token refresh automatically.
574
+ `;
575
+ return `
576
+ ${theme.green("Setup Codex CLI:")}
577
+
578
+ ` + `Add this to ${theme.blue("~/.codex/config.toml")}:
579
+
580
+ ` + `${configBlock}
581
+
582
+ ` + note;
583
+ }
584
+ case "opencode": {
585
+ const fileName = transport === "http" ? "opencode.json" : "opencode.json";
586
+ return baseInstructions + transportNote + `
587
+ ${theme.green("Setup OpenCode:")}
588
+
589
+ ` + `1. Add the above configuration to ${theme.blue(fileName)} in your project root
590
+ ` + `2. If using HTTP transport, OpenCode will automatically handle OAuth when you first use the MCP server
591
+ ` + `3. If using STDIO transport, make sure the proxy server is running with:
592
+ ` + ` ${theme.blue("npx @_davideast/stitch-mcp proxy")}
593
+
594
+ ` + `${theme.gray("Note:")} You can now use Stitch tools by adding "use the stitch tool" to your prompts.
595
+ `;
596
+ }
597
+ default:
598
+ return baseInstructions + transportNote + `
599
+ ${theme.yellow("Add this configuration to your MCP client.")}
600
+ `;
601
+ }
602
+ }
603
+ }
604
+
605
+ // src/ui/checklist/handler.ts
606
+ var STATE_ICONS = {
607
+ PENDING: "○",
608
+ IN_PROGRESS: "▸",
609
+ COMPLETE: "✓",
610
+ SKIPPED: "−",
611
+ FAILED: "✗"
612
+ };
613
+
614
+ class ChecklistUIHandler {
615
+ config;
616
+ states = new Map;
617
+ lastOutputLines = 0;
618
+ initialize(config) {
619
+ this.config = config;
620
+ this.states.clear();
621
+ const initItem = (item) => {
622
+ this.states.set(item.id, { state: "PENDING" });
623
+ item.children?.forEach(initItem);
624
+ };
625
+ config.items.forEach(initItem);
626
+ }
627
+ updateItem(input) {
628
+ const current = this.states.get(input.itemId);
629
+ if (!current) {
630
+ return {
631
+ success: false,
632
+ error: {
633
+ code: "ITEM_NOT_FOUND",
634
+ message: `Item "${input.itemId}" not found`,
635
+ recoverable: false
636
+ }
637
+ };
638
+ }
639
+ const previousState = current.state;
640
+ this.states.set(input.itemId, {
641
+ state: input.state,
642
+ detail: input.detail,
643
+ reason: input.reason
644
+ });
645
+ return {
646
+ success: true,
647
+ data: {
648
+ itemId: input.itemId,
649
+ previousState,
650
+ newState: input.state
651
+ }
652
+ };
653
+ }
654
+ render() {
655
+ try {
656
+ const lines = [];
657
+ const { completed, total, percent } = this.getProgress();
658
+ lines.push(`\uD83E\uDDF5 ${this.config.title}`);
659
+ lines.push("");
660
+ this.config.items.forEach((item, idx) => {
661
+ const state = this.states.get(item.id);
662
+ const icon = STATE_ICONS[state.state];
663
+ const color = this.getStateColor(state.state);
664
+ let line = ` ${color(icon)} ${idx + 1}. ${item.label}`;
665
+ if (state.detail) {
666
+ line += ` ${theme.gray("·")} ${state.detail}`;
667
+ }
668
+ lines.push(line);
669
+ if (state.reason) {
670
+ lines.push(` └─ ${theme.gray(state.reason)}`);
671
+ }
672
+ });
673
+ if (this.config.showProgress) {
674
+ lines.push("");
675
+ const barWidth = 40;
676
+ const filled = Math.round(percent / 100 * barWidth);
677
+ const bar = "━".repeat(filled) + "─".repeat(barWidth - filled);
678
+ lines.push(` ${bar} ${percent}%`);
679
+ }
680
+ return {
681
+ success: true,
682
+ data: {
683
+ output: lines.join(`
684
+ `),
685
+ completedCount: completed,
686
+ totalCount: total,
687
+ percentComplete: percent
688
+ }
689
+ };
690
+ } catch (error) {
691
+ return {
692
+ success: false,
693
+ error: {
694
+ code: "RENDER_FAILED",
695
+ message: error instanceof Error ? error.message : String(error),
696
+ recoverable: false
697
+ }
698
+ };
699
+ }
700
+ }
701
+ print(options) {
702
+ if (options?.clearPrevious && this.lastOutputLines > 0) {
703
+ process.stdout.write(`\x1B[${this.lastOutputLines}A\x1B[0J`);
704
+ }
705
+ const result = this.render();
706
+ if (result.success) {
707
+ console.log(result.data.output);
708
+ this.lastOutputLines = result.data.output.split(`
709
+ `).length;
710
+ }
711
+ }
712
+ getProgress() {
713
+ const total = this.states.size;
714
+ let completed = 0;
715
+ this.states.forEach((state) => {
716
+ if (state.state === "COMPLETE" || state.state === "SKIPPED") {
717
+ completed++;
718
+ }
719
+ });
720
+ return {
721
+ completed,
722
+ total,
723
+ percent: Math.round(completed / total * 100)
724
+ };
725
+ }
726
+ isComplete() {
727
+ for (const state of this.states.values()) {
728
+ if (state.state === "PENDING" || state.state === "IN_PROGRESS") {
729
+ return false;
730
+ }
731
+ }
732
+ return true;
733
+ }
734
+ getStateColor(state) {
735
+ switch (state) {
736
+ case "COMPLETE":
737
+ return theme.green;
738
+ case "SKIPPED":
739
+ return theme.gray;
740
+ case "FAILED":
741
+ return theme.red;
742
+ case "IN_PROGRESS":
743
+ return theme.yellow;
744
+ default:
745
+ return theme.gray;
746
+ }
747
+ }
748
+ }
749
+
750
+ // src/commands/init/steps/ClientSelectionStep.ts
751
+ class ClientSelectionStep {
752
+ id = "mcp-client";
753
+ name = "Select MCP client";
754
+ async shouldRun(context) {
755
+ return true;
756
+ }
757
+ async run(context) {
758
+ if (context.input.client) {
759
+ try {
760
+ context.mcpClient = this.resolveMcpClient(context.input.client);
761
+ return {
762
+ success: true,
763
+ detail: context.mcpClient,
764
+ status: "SKIPPED",
765
+ reason: "Set via --client flag"
766
+ };
767
+ } catch (e) {
768
+ return {
769
+ success: false,
770
+ error: e instanceof Error ? e : new Error(String(e))
771
+ };
772
+ }
773
+ }
774
+ context.mcpClient = await context.ui.promptMcpClient();
775
+ return {
776
+ success: true,
777
+ detail: context.mcpClient
778
+ };
779
+ }
780
+ resolveMcpClient(input) {
781
+ const map = {
782
+ antigravity: "antigravity",
783
+ agy: "antigravity",
784
+ vscode: "vscode",
785
+ vsc: "vscode",
786
+ cursor: "cursor",
787
+ cur: "cursor",
788
+ "claude-code": "claude-code",
789
+ cc: "claude-code",
790
+ "gemini-cli": "gemini-cli",
791
+ gcli: "gemini-cli",
792
+ codex: "codex",
793
+ cdx: "codex",
794
+ opencode: "opencode",
795
+ opc: "opencode"
796
+ };
797
+ const normalized = input.trim().toLowerCase();
798
+ const client = map[normalized];
799
+ if (!client) {
800
+ throw new Error(`Invalid client '${input}'. Supported: antigravity (agy), vscode (vsc), cursor (cur), claude-code (cc), gemini-cli (gcli), codex (cdx), opencode (opc)`);
801
+ }
802
+ return client;
803
+ }
804
+ }
805
+
806
+ // src/commands/init/steps/AuthModeStep.ts
807
+ import fs from "node:fs";
808
+ import path from "node:path";
809
+
810
+ class AuthModeStep {
811
+ id = "authentication-mode";
812
+ name = "Select Authentication Mode";
813
+ async shouldRun(context) {
814
+ return true;
815
+ }
816
+ async run(context) {
817
+ const authMode = await context.ui.promptAuthMode();
818
+ context.authMode = authMode;
819
+ if (authMode === "apiKey") {
820
+ const storage = await context.ui.promptApiKeyStorage();
821
+ if (storage === "config") {
822
+ context.apiKey = await context.ui.promptApiKey();
823
+ } else if (storage === "skip") {
824
+ context.apiKey = "YOUR-API-KEY";
825
+ } else if (storage === ".env") {
826
+ const inputKey = await context.ui.promptApiKey();
827
+ context.apiKey = "YOUR-API-KEY";
828
+ const envPath = path.join(process.cwd(), ".env");
829
+ const envContent = `
830
+ STITCH_API_KEY=${inputKey}
831
+ `;
832
+ try {
833
+ await fs.promises.writeFile(envPath, envContent, { flag: "a", mode: 384 });
834
+ const gitignorePath = path.join(process.cwd(), ".gitignore");
835
+ try {
836
+ const gitignoreContent = await fs.promises.readFile(gitignorePath, "utf8");
837
+ if (!gitignoreContent.includes(".env")) {
838
+ await fs.promises.appendFile(gitignorePath, `
839
+ .env
840
+ `);
841
+ }
842
+ } catch (err) {
843
+ if (err.code === "ENOENT") {
844
+ await fs.promises.writeFile(gitignorePath, `.env
845
+ `);
846
+ } else {
847
+ throw err;
848
+ }
849
+ }
850
+ } catch (e) {
851
+ context.ui.warn(`Warning: Failed to update .env or .gitignore: ${e instanceof Error ? e.message : String(e)}`);
852
+ }
853
+ }
854
+ return {
855
+ success: true,
856
+ detail: "API Key",
857
+ status: "COMPLETE"
858
+ };
859
+ }
860
+ return {
861
+ success: true,
862
+ detail: "OAuth",
863
+ status: "COMPLETE"
864
+ };
865
+ }
866
+ }
867
+
868
+ // src/commands/init/steps/GcloudInstallStep.ts
869
+ class GcloudInstallStep {
870
+ id = "gcloud-cli";
871
+ name = "Install Google Cloud CLI";
872
+ async shouldRun(context) {
873
+ return context.authMode !== "apiKey";
874
+ }
875
+ async run(context) {
876
+ if (context.authMode === "apiKey") {
877
+ return { success: true, status: "SKIPPED", reason: "Not required for API Key" };
878
+ }
879
+ const gcloudResult = await context.gcloudService.ensureInstalled({
880
+ minVersion: "400.0.0",
881
+ forceLocal: context.input.local
882
+ });
883
+ if (!gcloudResult.success) {
884
+ return {
885
+ success: false,
886
+ error: new Error(gcloudResult.error.message),
887
+ detail: gcloudResult.error.message
888
+ };
889
+ }
890
+ return {
891
+ success: true,
892
+ detail: `v${gcloudResult.data.version} (${gcloudResult.data.location})`
893
+ };
894
+ }
895
+ }
896
+
897
+ // src/platform/environment.ts
898
+ import fs2 from "node:fs";
899
+ function detectWSL() {
900
+ try {
901
+ const procVersion = fs2.readFileSync("/proc/version", "utf8").toLowerCase();
902
+ return procVersion.includes("microsoft") || procVersion.includes("wsl");
903
+ } catch {
904
+ return false;
905
+ }
906
+ }
907
+ function detectSSH() {
908
+ return Boolean(process.env.SSH_CLIENT || process.env.SSH_TTY || process.env.SSH_CONNECTION);
909
+ }
910
+ function detectDocker() {
911
+ try {
912
+ return fs2.existsSync("/.dockerenv");
913
+ } catch {
914
+ return false;
915
+ }
916
+ }
917
+ function detectCloudShell() {
918
+ return Boolean(process.env.CLOUD_SHELL);
919
+ }
920
+ function detectDisplay() {
921
+ return Boolean(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);
922
+ }
923
+ function detectEnvironment() {
924
+ const isWSL = detectWSL();
925
+ const isSSH = detectSSH();
926
+ const isDocker = detectDocker();
927
+ const isCloudShell = detectCloudShell();
928
+ const hasDisplay = detectDisplay();
929
+ let needsNoBrowser = false;
930
+ let reason;
931
+ if (isWSL) {
932
+ needsNoBrowser = true;
933
+ reason = "WSL detected - browser redirect to localhost may not work";
934
+ } else if (isSSH && !hasDisplay) {
935
+ needsNoBrowser = true;
936
+ reason = "SSH session without display forwarding";
937
+ } else if (isDocker) {
938
+ needsNoBrowser = true;
939
+ reason = "Docker container detected";
940
+ } else if (isCloudShell) {
941
+ needsNoBrowser = true;
942
+ reason = "Cloud Shell detected";
943
+ } else if (!hasDisplay) {
944
+ needsNoBrowser = true;
945
+ reason = "No display detected (headless environment)";
946
+ }
947
+ return {
948
+ isWSL,
949
+ isSSH,
950
+ isDocker,
951
+ isCloudShell,
952
+ hasDisplay,
953
+ needsNoBrowser,
954
+ reason
955
+ };
956
+ }
957
+
958
+ // src/commands/init/steps/AuthStep.ts
959
+ import path2 from "node:path";
960
+ class AuthStep {
961
+ id = "authentication";
962
+ name = "Authenticate with Google";
963
+ async shouldRun(context) {
964
+ return context.authMode !== "apiKey";
965
+ }
966
+ async run(context) {
967
+ if (context.authMode === "apiKey") {
968
+ return { success: true, status: "SKIPPED", reason: "Using API Key" };
969
+ }
970
+ const env = detectEnvironment();
971
+ if (env.needsNoBrowser && env.reason) {
972
+ context.ui.warn(`
973
+ ⚠ ${env.reason}`);
974
+ context.ui.log(` If browser auth fails, copy the URL from terminal and open manually.
975
+ `);
976
+ }
977
+ const existingAccount = await context.gcloudService.getActiveAccount();
978
+ const hasADC = await context.gcloudService.hasADC();
979
+ if (existingAccount && hasADC) {
980
+ context.authAccount = existingAccount;
981
+ return {
982
+ success: true,
983
+ detail: existingAccount,
984
+ status: "SKIPPED",
985
+ reason: "Already authenticated"
986
+ };
987
+ }
988
+ const gcloudInfo = await context.gcloudService.ensureInstalled({
989
+ minVersion: "400.0.0",
990
+ forceLocal: context.input.local
991
+ });
992
+ if (!gcloudInfo.success)
993
+ return { success: false, error: new Error("Gcloud not found") };
994
+ const isBundled = gcloudInfo.data.location === "bundled";
995
+ const gcloudBinDir = path2.dirname(gcloudInfo.data.path);
996
+ let configPrefix = "";
997
+ if (isBundled) {
998
+ const configPath = path2.dirname(gcloudBinDir) + "/../config";
999
+ configPrefix = `CLOUDSDK_CONFIG="${configPath}"`;
1000
+ context.ui.warn(`
1001
+ Configure gcloud PATH
1002
+ `);
1003
+ context.ui.log(` Open a NEW terminal tab/window and run this command:
1004
+ `);
1005
+ context.ui.log(theme.cyan(` export PATH="${gcloudBinDir}:$PATH"
1006
+ `));
1007
+ try {
1008
+ const { default: clipboard } = await import("./chunk-7tx0wn04.js");
1009
+ await clipboard.write(`export PATH="${gcloudBinDir}:$PATH"`);
1010
+ context.ui.log(theme.gray(" (copied to clipboard)"));
1011
+ } catch {}
1012
+ await context.ui.promptConfirm("Press Enter when complete", true);
1013
+ }
1014
+ if (!existingAccount) {
1015
+ context.ui.warn(`
1016
+ Authenticate with Google Cloud
1017
+ `);
1018
+ context.ui.log(theme.cyan(` ${configPrefix} gcloud auth login
1019
+ `));
1020
+ await context.ui.promptConfirm("Press Enter when complete", true);
1021
+ }
1022
+ if (!hasADC) {
1023
+ context.ui.warn(`
1024
+ Authorize Application Default Credentials
1025
+ `);
1026
+ context.ui.log(theme.cyan(` ${configPrefix} gcloud auth application-default login
1027
+ `));
1028
+ await context.ui.promptConfirm("Press Enter when complete", true);
1029
+ }
1030
+ const verifyAccount = await context.gcloudService.getActiveAccount();
1031
+ if (!verifyAccount) {
1032
+ return {
1033
+ success: false,
1034
+ error: new Error("No authenticated account found after setup"),
1035
+ detail: "No account found",
1036
+ errorCode: "AUTH_FAILED"
1037
+ };
1038
+ }
1039
+ context.authAccount = verifyAccount;
1040
+ return {
1041
+ success: true,
1042
+ detail: verifyAccount
1043
+ };
1044
+ }
1045
+ }
1046
+
1047
+ // src/commands/init/steps/TransportStep.ts
1048
+ class TransportStep {
1049
+ id = "connection-method";
1050
+ name = "Choose connection method";
1051
+ async shouldRun(context) {
1052
+ return true;
1053
+ }
1054
+ async run(context) {
1055
+ if (context.input.transport) {
1056
+ context.transport = this.resolveTransport(context.input.transport);
1057
+ const transportLabel2 = context.transport === "http" ? "Direct" : "Proxy";
1058
+ return {
1059
+ success: true,
1060
+ detail: transportLabel2,
1061
+ status: "SKIPPED",
1062
+ reason: "Set via --transport flag"
1063
+ };
1064
+ }
1065
+ context.transport = await context.ui.promptTransportType(context.authMode);
1066
+ const transportLabel = context.transport === "http" ? "Direct" : "Proxy";
1067
+ return {
1068
+ success: true,
1069
+ detail: transportLabel
1070
+ };
1071
+ }
1072
+ resolveTransport(input) {
1073
+ const normalized = input.trim().toLowerCase();
1074
+ if (normalized === "http")
1075
+ return "http";
1076
+ if (normalized === "stdio")
1077
+ return "stdio";
1078
+ throw new Error(`Invalid transport '${input}'. Supported: http, stdio`);
1079
+ }
1080
+ }
1081
+
1082
+ // src/commands/init/steps/ProjectSelectStep.ts
1083
+ class ProjectSelectStep {
1084
+ id = "project-selection";
1085
+ name = "Select Google Cloud project";
1086
+ async shouldRun(context) {
1087
+ return context.authMode !== "apiKey";
1088
+ }
1089
+ async run(context) {
1090
+ if (context.authMode === "apiKey") {
1091
+ return { success: true, status: "SKIPPED", reason: "Not required for API Key" };
1092
+ }
1093
+ let projectResult = null;
1094
+ const activeProjectId = await context.gcloudService.getProjectId();
1095
+ if (activeProjectId) {
1096
+ const detailsResult = await context.projectService.getProjectDetails({ projectId: activeProjectId });
1097
+ if (detailsResult.success) {
1098
+ const useActive = context.input.defaults || context.input.autoVerify ? true : await context.ui.promptConfirm(`Use active project: ${detailsResult.data.name} (${detailsResult.data.projectId})?`, true);
1099
+ if (useActive) {
1100
+ projectResult = detailsResult;
1101
+ }
1102
+ }
1103
+ }
1104
+ if (!projectResult) {
1105
+ projectResult = await context.projectService.selectProject({
1106
+ allowSearch: true,
1107
+ limit: 5
1108
+ });
1109
+ }
1110
+ if (!projectResult.success) {
1111
+ const error = projectResult.error || { message: "Unknown error" };
1112
+ return { success: false, error: new Error(error.message) };
1113
+ }
1114
+ const setProjectResult = await context.gcloudService.setProject({
1115
+ projectId: projectResult.data.projectId
1116
+ });
1117
+ if (!setProjectResult.success) {
1118
+ const error = setProjectResult.error || { message: "Unknown error" };
1119
+ return { success: false, error: new Error(error.message) };
1120
+ }
1121
+ context.projectId = projectResult.data.projectId;
1122
+ return {
1123
+ success: true,
1124
+ detail: context.projectId
1125
+ };
1126
+ }
1127
+ }
1128
+
1129
+ // src/commands/init/steps/IamApiStep.ts
1130
+ class IamApiStep {
1131
+ id = "iam-and-api";
1132
+ name = "Configure IAM & enable API";
1133
+ async shouldRun(context) {
1134
+ return context.authMode !== "apiKey";
1135
+ }
1136
+ async run(context) {
1137
+ if (context.authMode === "apiKey") {
1138
+ return { success: true, status: "SKIPPED", reason: "Not required for API Key" };
1139
+ }
1140
+ if (!context.projectId || !context.authAccount) {
1141
+ return { success: false, error: new Error("Project ID or Auth Account missing") };
1142
+ }
1143
+ const hasIAMRole = await context.stitchService.checkIAMRole({
1144
+ projectId: context.projectId,
1145
+ userEmail: context.authAccount
1146
+ });
1147
+ if (!hasIAMRole) {
1148
+ const shouldConfigureIam = context.input.autoVerify || await context.ui.promptConfirm("Add the required IAM role to your account?", true);
1149
+ if (shouldConfigureIam) {
1150
+ await context.stitchService.configureIAM({
1151
+ projectId: context.projectId,
1152
+ userEmail: context.authAccount
1153
+ });
1154
+ }
1155
+ }
1156
+ await context.gcloudService.installBetaComponents();
1157
+ const isApiEnabled = await context.stitchService.checkAPIEnabled({
1158
+ projectId: context.projectId
1159
+ });
1160
+ if (!isApiEnabled) {
1161
+ await context.stitchService.enableAPI({
1162
+ projectId: context.projectId
1163
+ });
1164
+ }
1165
+ context.accessToken = await context.gcloudService.getAccessToken() || undefined;
1166
+ if (!context.accessToken) {
1167
+ return { success: false, error: new Error("Could not obtain access token") };
1168
+ }
1169
+ return { success: true, detail: "Ready" };
1170
+ }
1171
+ }
1172
+
1173
+ // src/commands/init/steps/ConfigStep.ts
1174
+ import fs3 from "node:fs";
1175
+ import os from "node:os";
1176
+ import path3 from "node:path";
1177
+
1178
+ class ConfigStep {
1179
+ id = "mcp-config";
1180
+ name = "Generate MCP configuration";
1181
+ async shouldRun(context) {
1182
+ return true;
1183
+ }
1184
+ async run(context) {
1185
+ if (context.mcpClient === "gemini-cli") {
1186
+ await this.setupGeminiExtension(context);
1187
+ }
1188
+ const configResult = await context.mcpConfigService.generateConfig({
1189
+ client: context.mcpClient,
1190
+ projectId: context.projectId || "ignored-project-id",
1191
+ accessToken: context.accessToken,
1192
+ transport: context.transport,
1193
+ authMode: context.authMode,
1194
+ apiKey: context.apiKey
1195
+ });
1196
+ if (!configResult.success) {
1197
+ const error = configResult.error || { message: "Unknown error" };
1198
+ return { success: false, error: new Error(error.message) };
1199
+ }
1200
+ context.instructions = configResult.data.instructions;
1201
+ context.finalConfig = configResult.data.config;
1202
+ return { success: true, detail: "Generated" };
1203
+ }
1204
+ async setupGeminiExtension(context) {
1205
+ const spinner = createSpinner();
1206
+ const extensionPath = path3.join(os.homedir(), ".gemini", "extensions", "Stitch", "gemini-extension.json");
1207
+ let isInstalled = false;
1208
+ try {
1209
+ await fs3.promises.access(extensionPath);
1210
+ isInstalled = true;
1211
+ } catch {
1212
+ isInstalled = false;
1213
+ }
1214
+ if (isInstalled) {
1215
+ spinner.succeed("Stitch extension is already installed");
1216
+ } else {
1217
+ context.ui.log(theme.gray(" > gemini extensions install https://github.com/gemini-cli-extensions/stitch"));
1218
+ const shouldInstall = await context.ui.promptConfirm("Run this command?", true);
1219
+ if (shouldInstall) {
1220
+ spinner.start("Installing Stitch extension...");
1221
+ const installResult = await execCommand(["gemini", "extensions", "install", "https://github.com/gemini-cli-extensions/stitch"]);
1222
+ if (!installResult.success) {
1223
+ spinner.fail("Failed to install Stitch extension");
1224
+ context.ui.log(theme.red(` Error: ${installResult.stderr || installResult.error}`));
1225
+ context.ui.log(theme.gray(" Attempting to configure existing extension..."));
1226
+ } else {
1227
+ spinner.succeed("Extension installed");
1228
+ }
1229
+ }
1230
+ }
1231
+ spinner.start("Configuring extension...");
1232
+ try {
1233
+ await fs3.promises.access(extensionPath);
1234
+ } catch {
1235
+ spinner.fail("Extension configuration file not found");
1236
+ context.ui.log(theme.gray(` Expected path: ${extensionPath}`));
1237
+ return;
1238
+ }
1239
+ try {
1240
+ const content = await fs3.promises.readFile(extensionPath, "utf8");
1241
+ const config = JSON.parse(content);
1242
+ if (!config.mcpServers?.stitch) {
1243
+ spinner.fail("Invalid extension configuration format detected");
1244
+ return;
1245
+ }
1246
+ if (context.transport === "stdio") {
1247
+ const env = {
1248
+ PATH: process.env.PATH || ""
1249
+ };
1250
+ if (context.apiKey) {
1251
+ env.STITCH_API_KEY = context.apiKey;
1252
+ } else {
1253
+ env.STITCH_PROJECT_ID = context.projectId;
1254
+ }
1255
+ config.mcpServers.stitch = {
1256
+ command: "npx",
1257
+ args: ["@_davideast/stitch-mcp", "proxy"],
1258
+ env
1259
+ };
1260
+ await fs3.promises.writeFile(extensionPath, JSON.stringify(config, null, 4));
1261
+ const successMsg = context.apiKey ? "Stitch extension configured for STDIO with API Key" : `Stitch extension configured for STDIO: Project ID set to ${theme.blue(context.projectId)}`;
1262
+ spinner.succeed(successMsg);
1263
+ } else {
1264
+ const existingHeaders = config.mcpServers.stitch.headers || {};
1265
+ if (context.apiKey) {
1266
+ config.mcpServers.stitch = {
1267
+ url: "https://stitch.googleapis.com/mcp",
1268
+ headers: {
1269
+ ...existingHeaders,
1270
+ "X-Goog-Api-Key": context.apiKey
1271
+ }
1272
+ };
1273
+ if (config.mcpServers.stitch.headers["Authorization"])
1274
+ delete config.mcpServers.stitch.headers["Authorization"];
1275
+ if (config.mcpServers.stitch.headers["X-Goog-User-Project"])
1276
+ delete config.mcpServers.stitch.headers["X-Goog-User-Project"];
1277
+ await fs3.promises.writeFile(extensionPath, JSON.stringify(config, null, 4));
1278
+ spinner.succeed(`Stitch extension configured for HTTP with API Key`);
1279
+ } else {
1280
+ config.mcpServers.stitch = {
1281
+ url: "https://stitch.googleapis.com/mcp",
1282
+ headers: {
1283
+ Authorization: "Bearer $STITCH_ACCESS_TOKEN",
1284
+ ...existingHeaders,
1285
+ "X-Goog-User-Project": context.projectId
1286
+ }
1287
+ };
1288
+ await fs3.promises.writeFile(extensionPath, JSON.stringify(config, null, 4));
1289
+ spinner.succeed(`Stitch extension configured for HTTP: Project ID set to ${theme.blue(context.projectId)}`);
1290
+ }
1291
+ }
1292
+ context.ui.log(theme.gray(` File: ${extensionPath}`));
1293
+ } catch (e) {
1294
+ spinner.fail("Failed to update extension configuration");
1295
+ context.ui.log(theme.red(` Error: ${e instanceof Error ? e.message : String(e)}`));
1296
+ }
1297
+ }
1298
+ }
1299
+
1300
+ // src/commands/init/steps/TestConnectionStep.ts
1301
+ class TestConnectionStep {
1302
+ id = "connection-test";
1303
+ name = "Test connection";
1304
+ async shouldRun(context) {
1305
+ return context.authMode === "oauth";
1306
+ }
1307
+ async run(context) {
1308
+ if (context.authMode !== "oauth") {
1309
+ return { success: true, status: "SKIPPED", reason: "Not supported for API Key yet" };
1310
+ }
1311
+ if (!context.accessToken) {
1312
+ return { success: false, status: "SKIPPED", reason: "No access token" };
1313
+ }
1314
+ const testResult = await context.stitchService.testConnection({
1315
+ projectId: context.projectId,
1316
+ accessToken: context.accessToken
1317
+ });
1318
+ if (!testResult.success) {
1319
+ const error = testResult.error || { message: "Unknown error", suggestion: "" };
1320
+ context.ui.log(theme.red(`
1321
+ ${icons.error} Error: ${error.message}`));
1322
+ context.ui.warn(` ${error.suggestion}`);
1323
+ return {
1324
+ success: false,
1325
+ detail: error.message,
1326
+ error: new Error(error.message)
1327
+ };
1328
+ }
1329
+ return { success: true, detail: `${testResult.data.statusCode} OK` };
1330
+ }
1331
+ }
1332
+
1333
+ // src/commands/init/handler.ts
1334
+ class InitHandler {
1335
+ gcloudService;
1336
+ mcpConfigService;
1337
+ projectService;
1338
+ stitchService;
1339
+ ui;
1340
+ checklist;
1341
+ steps;
1342
+ constructor(gcloudService, mcpConfigService, projectService, stitchService, ui) {
1343
+ this.gcloudService = gcloudService || new GcloudHandler;
1344
+ this.mcpConfigService = mcpConfigService || new McpConfigHandler;
1345
+ this.projectService = projectService || new ProjectHandler(this.gcloudService);
1346
+ this.stitchService = stitchService || new StitchHandler;
1347
+ this.checklist = new ChecklistUIHandler;
1348
+ this.ui = ui || new ConsoleUI;
1349
+ this.steps = [
1350
+ new ClientSelectionStep,
1351
+ new AuthModeStep,
1352
+ new GcloudInstallStep,
1353
+ new AuthStep,
1354
+ new TransportStep,
1355
+ new ProjectSelectStep,
1356
+ new IamApiStep,
1357
+ new ConfigStep,
1358
+ new TestConnectionStep
1359
+ ];
1360
+ }
1361
+ async execute(input) {
1362
+ this.checklist.initialize({
1363
+ title: "Stitch MCP Setup",
1364
+ items: this.steps.map((s) => ({ id: s.id, label: s.name })),
1365
+ showProgress: true,
1366
+ animationDelayMs: 100
1367
+ });
1368
+ console.log(`
1369
+ ${theme.blue("\uD83E\uDDF5 Stitch MCP Setup")}
1370
+ `);
1371
+ const context = {
1372
+ input,
1373
+ ui: this.ui,
1374
+ gcloudService: this.gcloudService,
1375
+ mcpConfigService: this.mcpConfigService,
1376
+ projectService: this.projectService,
1377
+ stitchService: this.stitchService
1378
+ };
1379
+ try {
1380
+ const { stoppedAt } = await runSteps(this.steps, context, {
1381
+ onBeforeStep: (step) => this.updateStep(step.id, "IN_PROGRESS"),
1382
+ onAfterStep: (step, result) => {
1383
+ if (!result.success) {
1384
+ const message = result.error?.message || result.detail || "Failed";
1385
+ this.updateStep(step.id, "FAILED", message);
1386
+ return true;
1387
+ }
1388
+ const status = result.status || "COMPLETE";
1389
+ this.updateStep(step.id, status, result.detail, result.reason);
1390
+ return false;
1391
+ },
1392
+ onSkippedStep: (step) => this.updateStep(step.id, "SKIPPED", "Not required")
1393
+ });
1394
+ if (stoppedAt) {
1395
+ const message = stoppedAt.result.error?.message || stoppedAt.result.detail || "Failed";
1396
+ return {
1397
+ success: false,
1398
+ error: {
1399
+ code: stoppedAt.result.errorCode || "UNKNOWN_ERROR",
1400
+ message,
1401
+ recoverable: true
1402
+ }
1403
+ };
1404
+ }
1405
+ const { percent } = this.checklist.getProgress();
1406
+ const barWidth = 40;
1407
+ const filled = Math.round(percent / 100 * barWidth);
1408
+ const bar = "━".repeat(filled) + "─".repeat(barWidth - filled);
1409
+ console.log(`
1410
+ ${bar} ${percent}%`);
1411
+ if (this.checklist.isComplete()) {
1412
+ console.log(`
1413
+ ${theme.green("\uD83C\uDF89 Setup complete!")}
1414
+ `);
1415
+ }
1416
+ if (context.instructions) {
1417
+ console.log(context.instructions);
1418
+ }
1419
+ return {
1420
+ success: true,
1421
+ data: {
1422
+ projectId: context.projectId || "ignored",
1423
+ mcpConfig: context.finalConfig || "",
1424
+ instructions: context.instructions || ""
1425
+ }
1426
+ };
1427
+ } catch (error) {
1428
+ return {
1429
+ success: false,
1430
+ error: {
1431
+ code: "UNKNOWN_ERROR",
1432
+ message: error instanceof Error ? error.message : String(error),
1433
+ recoverable: false
1434
+ }
1435
+ };
1436
+ }
1437
+ }
1438
+ updateStep(stepId, state, detail, reason) {
1439
+ this.checklist.updateItem({ itemId: stepId, state, detail, reason });
1440
+ if (state !== "IN_PROGRESS") {
1441
+ const step = this.steps.find((s) => s.id === stepId);
1442
+ this.printStepResult(stepId, step?.name || stepId, state, detail, reason);
1443
+ }
1444
+ }
1445
+ printStepResult(stepId, label, state, detail, reason) {
1446
+ const stepIndex = this.steps.findIndex((s) => s.id === stepId);
1447
+ const stepNum = stepIndex + 1;
1448
+ const icons2 = {
1449
+ PENDING: "○",
1450
+ IN_PROGRESS: "▸",
1451
+ COMPLETE: "✓",
1452
+ SKIPPED: "−",
1453
+ FAILED: "✗"
1454
+ };
1455
+ const icon = icons2[state];
1456
+ const colors = {
1457
+ PENDING: theme.gray,
1458
+ IN_PROGRESS: theme.yellow,
1459
+ COMPLETE: theme.green,
1460
+ SKIPPED: theme.gray,
1461
+ FAILED: theme.red
1462
+ };
1463
+ const color = colors[state];
1464
+ let line = ` ${color(icon)} ${stepNum}. ${label}`;
1465
+ if (detail) {
1466
+ line += ` ${theme.gray("·")} ${detail}`;
1467
+ }
1468
+ console.log(line);
1469
+ if (reason) {
1470
+ console.log(` └─ ${theme.gray(reason)}`);
1471
+ }
1472
+ }
1473
+ }
1474
+
1475
+ export { ProjectHandler, McpConfigHandler, InitHandler };
1476
+
1477
+ //# debugId=6A72E5B4C29CCB5764756E2164756E21