@_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.
- package/README.md +54 -239
- package/dist/chunk-25wakzyb.js +137 -0
- package/dist/chunk-25wakzyb.js.map +10 -0
- package/dist/chunk-2cetsfw4.js +16531 -0
- package/dist/chunk-2cetsfw4.js.map +95 -0
- package/dist/chunk-2fcdwvrm.js +54 -0
- package/dist/chunk-2fcdwvrm.js.map +10 -0
- package/dist/chunk-2k7n0w2x.js +3980 -0
- package/dist/chunk-2k7n0w2x.js.map +16 -0
- package/dist/chunk-2kyqsrg2.js +403 -0
- package/dist/chunk-2kyqsrg2.js.map +10 -0
- package/dist/chunk-384jmtpy.js +11057 -0
- package/dist/chunk-384jmtpy.js.map +238 -0
- package/dist/chunk-3at4pjgn.js +22 -0
- package/dist/chunk-3at4pjgn.js.map +9 -0
- package/dist/chunk-3sfn889r.js +492 -0
- package/dist/chunk-3sfn889r.js.map +13 -0
- package/dist/chunk-45wx7tn7.js +94 -0
- package/dist/chunk-45wx7tn7.js.map +10 -0
- package/dist/chunk-48d17n29.js +10 -0
- package/dist/chunk-48d17n29.js.map +9 -0
- package/dist/chunk-4jwmvjb4.js +839 -0
- package/dist/chunk-4jwmvjb4.js.map +11 -0
- package/dist/chunk-4jygt4d6.js +14 -0
- package/dist/chunk-4jygt4d6.js.map +10 -0
- package/dist/chunk-4vxy1qce.js +68 -0
- package/dist/chunk-4vxy1qce.js.map +10 -0
- package/dist/chunk-7tx0wn04.js +11 -0
- package/dist/chunk-7tx0wn04.js.map +9 -0
- package/dist/chunk-7xh1y383.js +45188 -0
- package/dist/chunk-7xh1y383.js.map +258 -0
- package/dist/chunk-8yrtq2qs.js +18 -0
- package/dist/chunk-8yrtq2qs.js.map +9 -0
- package/dist/chunk-985f11w6.js +21 -0
- package/dist/chunk-985f11w6.js.map +9 -0
- package/dist/chunk-9tvppjaf.js +250 -0
- package/dist/chunk-9tvppjaf.js.map +14 -0
- package/dist/chunk-9wyra8hs.js +32 -0
- package/dist/chunk-9wyra8hs.js.map +9 -0
- package/dist/chunk-cwkb2wbe.js +733 -0
- package/dist/chunk-cwkb2wbe.js.map +16 -0
- package/dist/chunk-djhzzcgj.js +362 -0
- package/dist/chunk-djhzzcgj.js.map +13 -0
- package/dist/chunk-edp6faw2.js +7 -0
- package/dist/chunk-edp6faw2.js.map +9 -0
- package/dist/chunk-ezmw2j8c.js +14 -0
- package/dist/chunk-ezmw2j8c.js.map +9 -0
- package/dist/chunk-f2hq6bfv.js +22 -0
- package/dist/chunk-f2hq6bfv.js.map +10 -0
- package/dist/chunk-fwb4fnkp.js +31 -0
- package/dist/chunk-fwb4fnkp.js.map +10 -0
- package/dist/chunk-h18jrqed.js +9517 -0
- package/dist/chunk-h18jrqed.js.map +99 -0
- package/dist/chunk-hb3c6f6a.js +42 -0
- package/dist/chunk-hb3c6f6a.js.map +9 -0
- package/dist/chunk-jy2d17pr.js +252 -0
- package/dist/chunk-jy2d17pr.js.map +11 -0
- package/dist/chunk-kbtqrkwh.js +24 -0
- package/dist/chunk-kbtqrkwh.js.map +10 -0
- package/dist/chunk-knbnsf6s.js +92 -0
- package/dist/chunk-knbnsf6s.js.map +10 -0
- package/dist/chunk-mv9ssgmx.js +446 -0
- package/dist/chunk-mv9ssgmx.js.map +17 -0
- package/dist/chunk-nq68kghz.js +1647 -0
- package/dist/chunk-nq68kghz.js.map +10 -0
- package/dist/chunk-nthabjd9.js +3138 -0
- package/dist/chunk-nthabjd9.js.map +34 -0
- package/dist/chunk-nxpzt33t.js +278 -0
- package/dist/chunk-nxpzt33t.js.map +10 -0
- package/dist/chunk-pfyjtfex.js +172 -0
- package/dist/chunk-pfyjtfex.js.map +10 -0
- package/dist/chunk-q1nd6g0y.js +392 -0
- package/dist/chunk-q1nd6g0y.js.map +20 -0
- package/dist/chunk-q4js8r0z.js +4708 -0
- package/dist/chunk-q4js8r0z.js.map +29 -0
- package/dist/chunk-qv44tmn6.js +289 -0
- package/dist/chunk-qv44tmn6.js.map +13 -0
- package/dist/chunk-r2sg2nxa.js +20 -0
- package/dist/chunk-r2sg2nxa.js.map +9 -0
- package/dist/chunk-rp8wjzht.js +16959 -0
- package/dist/chunk-rp8wjzht.js.map +26 -0
- package/dist/chunk-rpxnm86e.js +372 -0
- package/dist/chunk-rpxnm86e.js.map +17 -0
- package/dist/chunk-sdp429xd.js +914 -0
- package/dist/chunk-sdp429xd.js.map +24 -0
- package/dist/chunk-t85nbjjb.js +113 -0
- package/dist/chunk-t85nbjjb.js.map +10 -0
- package/dist/chunk-tz7wnw4s.js +211 -0
- package/dist/chunk-tz7wnw4s.js.map +11 -0
- package/dist/chunk-v7117ywx.js +1477 -0
- package/dist/chunk-v7117ywx.js.map +23 -0
- package/dist/chunk-w8q7nsm7.js +2098 -0
- package/dist/chunk-w8q7nsm7.js.map +44 -0
- package/dist/chunk-wa64nz8b.js +47 -0
- package/dist/chunk-wa64nz8b.js.map +10 -0
- package/dist/chunk-wz8d5vzb.js +234 -0
- package/dist/chunk-wz8d5vzb.js.map +11 -0
- package/dist/chunk-ycfxp056.js +677 -0
- package/dist/chunk-ycfxp056.js.map +17 -0
- package/dist/chunk-z9d2xc83.js +3256 -0
- package/dist/chunk-z9d2xc83.js.map +84 -0
- package/dist/cli.js +111 -125552
- package/dist/cli.js.map +19 -0
- package/dist/commands/autoload.d.ts +2 -0
- package/dist/commands/doctor/command.d.ts +2 -1
- package/dist/commands/doctor/command.js +82 -0
- package/dist/commands/doctor/command.js.map +11 -0
- package/dist/commands/doctor/spec.d.ts +16 -8
- package/dist/commands/init/command.d.ts +2 -1
- package/dist/commands/init/command.js +98 -0
- package/dist/commands/init/command.js.map +11 -0
- package/dist/commands/init/spec.d.ts +29 -9
- package/dist/commands/logout/command.d.ts +2 -1
- package/dist/commands/logout/command.js +79 -0
- package/dist/commands/logout/command.js.map +11 -0
- package/dist/commands/logout/spec.d.ts +11 -0
- package/dist/commands/proxy/command.d.ts +2 -1
- package/dist/commands/proxy/command.js +56 -0
- package/dist/commands/proxy/command.js.map +11 -0
- package/dist/commands/proxy/spec.d.ts +15 -0
- package/dist/commands/screens/command.d.ts +2 -1
- package/dist/commands/screens/command.js +61 -0
- package/dist/commands/screens/command.js.map +11 -0
- package/dist/commands/screens/spec.d.ts +9 -0
- package/dist/commands/serve/command.d.ts +2 -1
- package/dist/commands/serve/command.js +65 -0
- package/dist/commands/serve/command.js.map +11 -0
- package/dist/commands/serve/spec.d.ts +9 -0
- package/dist/commands/site/command.d.ts +2 -1
- package/dist/commands/site/command.js +53 -0
- package/dist/commands/site/command.js.map +11 -0
- package/dist/commands/site/spec.d.ts +15 -0
- package/dist/commands/snapshot/command.d.ts +2 -1
- package/dist/commands/snapshot/command.js +59 -0
- package/dist/commands/snapshot/command.js.map +11 -0
- package/dist/commands/tool/command.d.ts +2 -1
- package/dist/commands/tool/command.js +79 -0
- package/dist/commands/tool/command.js.map +11 -0
- package/dist/commands/tool/handler.d.ts +17 -0
- package/dist/commands/tool/spec.d.ts +17 -0
- package/dist/commands/tool/steps/ValidateToolStep.d.ts +8 -0
- package/dist/commands/tool/virtual-tools/index.d.ts +1 -0
- package/dist/commands/tool/virtual-tools/list-tools.d.ts +2 -0
- package/dist/commands/view/command.d.ts +2 -1
- package/dist/commands/view/command.js +52 -0
- package/dist/commands/view/command.js.map +11 -0
- package/dist/commands/view/spec.d.ts +24 -0
- package/dist/framework/CommandDefinition.d.ts +2 -2
- package/dist/index.js +26 -21668
- package/dist/index.js.map +9 -0
- package/dist/lib/services/site/schemas.d.ts +8 -8
- package/dist/services/config/handler.d.ts +12 -0
- package/dist/services/config/spec.d.ts +82 -0
- package/dist/services/gcloud/auth.d.ts +28 -0
- package/dist/services/gcloud/core.d.ts +21 -0
- package/dist/services/gcloud/handler.d.ts +6 -18
- package/dist/services/gcloud/install.d.ts +23 -0
- package/dist/services/gcloud/projects.d.ts +15 -0
- package/dist/services/gcloud/spec.d.ts +32 -24
- package/dist/services/mcp-client/client.d.ts +0 -5
- package/dist/services/mcp-client/spec.d.ts +6 -6
- package/dist/services/mcp-config/spec.d.ts +12 -12
- package/dist/services/project/spec.d.ts +4 -4
- package/dist/services/proxy/spec.d.ts +3 -3
- package/dist/services/stitch/api.d.ts +10 -0
- package/dist/services/stitch/connection.d.ts +5 -0
- package/dist/services/stitch/handler.d.ts +5 -3
- package/dist/services/stitch/iam.d.ts +11 -0
- package/dist/services/stitch/spec.d.ts +6 -6
- package/dist/services/view/spec.d.ts +7 -7
- package/dist/src/cli.d.ts +1 -0
- package/dist/src/commands/doctor/handler.d.ts +9 -0
- package/dist/src/commands/doctor/handler.test.d.ts +1 -0
- package/dist/src/commands/doctor/spec.d.ts +130 -0
- package/dist/src/commands/doctor/spec.test.d.ts +1 -0
- package/dist/src/commands/init/handler.d.ts +17 -0
- package/dist/src/commands/init/handler.test.d.ts +1 -0
- package/dist/src/commands/init/spec.d.ts +88 -0
- package/dist/src/commands/init/spec.test.d.ts +1 -0
- package/dist/src/commands/proxy/handler.d.ts +7 -0
- package/dist/src/commands/proxy/handler.test.d.ts +1 -0
- package/dist/src/index.d.ts +13 -0
- package/dist/src/platform/detector.d.ts +29 -0
- package/dist/src/platform/paths.d.ts +20 -0
- package/dist/src/platform/shell.d.ts +26 -0
- package/dist/src/services/gcloud/handler.d.ts +48 -0
- package/dist/src/services/gcloud/handler.test.d.ts +1 -0
- package/dist/src/services/gcloud/spec.d.ts +405 -0
- package/dist/src/services/mcp-config/handler.d.ts +12 -0
- package/dist/src/services/mcp-config/handler.test.d.ts +1 -0
- package/dist/src/services/mcp-config/spec.d.ts +88 -0
- package/dist/src/services/mcp-config/spec.test.d.ts +1 -0
- package/dist/src/services/project/handler.d.ts +11 -0
- package/dist/src/services/project/handler.test.d.ts +1 -0
- package/dist/src/services/project/spec.d.ts +86 -0
- package/dist/src/services/project/spec.test.d.ts +1 -0
- package/dist/src/services/proxy/handler.d.ts +15 -0
- package/dist/src/services/proxy/handler.test.d.ts +1 -0
- package/dist/src/services/proxy/spec.d.ts +83 -0
- package/dist/src/services/proxy/spec.test.d.ts +1 -0
- package/dist/src/services/stitch/handler.d.ts +15 -0
- package/dist/src/services/stitch/handler.test.d.ts +1 -0
- package/dist/src/services/stitch/spec.d.ts +262 -0
- package/dist/src/services/stitch/spec.test.d.ts +1 -0
- package/dist/src/ui/spinner.d.ts +11 -0
- package/dist/src/ui/theme.d.ts +18 -0
- package/dist/src/ui/wizard.d.ts +24 -0
- package/dist/tests/mocks/shell.d.ts +2 -0
- package/dist/ui/JsonTree.d.ts +1 -1
- package/package.json +2 -2
- package/dist/commands/registry.d.ts +0 -2
- package/dist/ui/copy-behaviors/index.d.ts +0 -8
- package/dist/ui/serve-behaviors/index.d.ts +0 -7
- /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
|