@gxp-dev/tools 2.0.63 → 2.0.65
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 +32 -31
- package/bin/gx-devtools.js +74 -54
- package/bin/lib/cli.js +23 -21
- package/bin/lib/commands/add-dependency.js +366 -325
- package/bin/lib/commands/assets.js +137 -139
- package/bin/lib/commands/build.js +169 -174
- package/bin/lib/commands/datastore.js +181 -183
- package/bin/lib/commands/dev.js +127 -131
- package/bin/lib/commands/extensions.js +147 -149
- package/bin/lib/commands/extract-config.js +73 -67
- package/bin/lib/commands/index.js +12 -12
- package/bin/lib/commands/init.js +342 -240
- package/bin/lib/commands/publish.js +69 -75
- package/bin/lib/commands/socket.js +69 -69
- package/bin/lib/commands/ssl.js +14 -14
- package/bin/lib/constants.js +10 -24
- package/bin/lib/tui/App.tsx +761 -705
- package/bin/lib/tui/components/AIPanel.tsx +191 -171
- package/bin/lib/tui/components/CommandInput.tsx +394 -343
- package/bin/lib/tui/components/GeminiPanel.tsx +175 -151
- package/bin/lib/tui/components/Header.tsx +23 -21
- package/bin/lib/tui/components/LogPanel.tsx +244 -220
- package/bin/lib/tui/components/TabBar.tsx +50 -48
- package/bin/lib/tui/components/WelcomeScreen.tsx +126 -71
- package/bin/lib/tui/index.tsx +37 -39
- package/bin/lib/tui/services/AIService.ts +518 -462
- package/bin/lib/tui/services/ExtensionService.ts +140 -129
- package/bin/lib/tui/services/GeminiService.ts +367 -337
- package/bin/lib/tui/services/ServiceManager.ts +344 -322
- package/bin/lib/tui/services/SocketService.ts +168 -168
- package/bin/lib/tui/services/ViteService.ts +88 -88
- package/bin/lib/tui/services/index.ts +47 -22
- package/bin/lib/utils/ai-scaffold.js +291 -280
- package/bin/lib/utils/extract-config.js +157 -140
- package/bin/lib/utils/files.js +82 -86
- package/bin/lib/utils/index.js +7 -7
- package/bin/lib/utils/paths.js +34 -34
- package/bin/lib/utils/prompts.js +194 -169
- package/bin/lib/utils/ssl.js +79 -81
- package/browser-extensions/README.md +0 -1
- package/browser-extensions/chrome/background.js +244 -237
- package/browser-extensions/chrome/content.js +32 -29
- package/browser-extensions/chrome/devtools.html +7 -7
- package/browser-extensions/chrome/devtools.js +19 -19
- package/browser-extensions/chrome/inspector.js +802 -767
- package/browser-extensions/chrome/manifest.json +71 -63
- package/browser-extensions/chrome/panel.html +674 -636
- package/browser-extensions/chrome/panel.js +722 -712
- package/browser-extensions/chrome/popup.html +586 -543
- package/browser-extensions/chrome/popup.js +282 -244
- package/browser-extensions/chrome/rules.json +1 -1
- package/browser-extensions/chrome/test-chrome.html +216 -136
- package/browser-extensions/chrome/test-mixed-content.html +284 -189
- package/browser-extensions/chrome/test-uri-pattern.html +221 -198
- package/browser-extensions/firefox/README.md +9 -6
- package/browser-extensions/firefox/background.js +221 -218
- package/browser-extensions/firefox/content.js +55 -52
- package/browser-extensions/firefox/debug-errors.html +386 -228
- package/browser-extensions/firefox/debug-https.html +153 -105
- package/browser-extensions/firefox/devtools.html +7 -7
- package/browser-extensions/firefox/devtools.js +23 -20
- package/browser-extensions/firefox/inspector.js +802 -767
- package/browser-extensions/firefox/manifest.json +68 -68
- package/browser-extensions/firefox/panel.html +674 -636
- package/browser-extensions/firefox/panel.js +722 -712
- package/browser-extensions/firefox/popup.html +572 -535
- package/browser-extensions/firefox/popup.js +281 -236
- package/browser-extensions/firefox/test-gramercy.html +170 -125
- package/browser-extensions/firefox/test-imports.html +59 -55
- package/browser-extensions/firefox/test-masking.html +231 -140
- package/browser-extensions/firefox/test-uri-pattern.html +221 -198
- package/dist/tui/App.d.ts +1 -1
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +154 -150
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/components/AIPanel.d.ts.map +1 -1
- package/dist/tui/components/AIPanel.js +42 -35
- package/dist/tui/components/AIPanel.js.map +1 -1
- package/dist/tui/components/CommandInput.d.ts +1 -1
- package/dist/tui/components/CommandInput.d.ts.map +1 -1
- package/dist/tui/components/CommandInput.js +92 -62
- package/dist/tui/components/CommandInput.js.map +1 -1
- package/dist/tui/components/GeminiPanel.d.ts.map +1 -1
- package/dist/tui/components/GeminiPanel.js +37 -30
- package/dist/tui/components/GeminiPanel.js.map +1 -1
- package/dist/tui/components/Header.d.ts.map +1 -1
- package/dist/tui/components/Header.js +1 -1
- package/dist/tui/components/Header.js.map +1 -1
- package/dist/tui/components/LogPanel.d.ts +1 -1
- package/dist/tui/components/LogPanel.d.ts.map +1 -1
- package/dist/tui/components/LogPanel.js +26 -24
- package/dist/tui/components/LogPanel.js.map +1 -1
- package/dist/tui/components/TabBar.d.ts +2 -2
- package/dist/tui/components/TabBar.d.ts.map +1 -1
- package/dist/tui/components/TabBar.js +11 -11
- package/dist/tui/components/TabBar.js.map +1 -1
- package/dist/tui/components/WelcomeScreen.d.ts.map +1 -1
- package/dist/tui/components/WelcomeScreen.js +6 -6
- package/dist/tui/components/WelcomeScreen.js.map +1 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +8 -8
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/services/AIService.d.ts +2 -2
- package/dist/tui/services/AIService.d.ts.map +1 -1
- package/dist/tui/services/AIService.js +165 -125
- package/dist/tui/services/AIService.js.map +1 -1
- package/dist/tui/services/ExtensionService.d.ts +1 -1
- package/dist/tui/services/ExtensionService.d.ts.map +1 -1
- package/dist/tui/services/ExtensionService.js +33 -26
- package/dist/tui/services/ExtensionService.js.map +1 -1
- package/dist/tui/services/GeminiService.d.ts +1 -1
- package/dist/tui/services/GeminiService.d.ts.map +1 -1
- package/dist/tui/services/GeminiService.js +87 -76
- package/dist/tui/services/GeminiService.js.map +1 -1
- package/dist/tui/services/ServiceManager.d.ts +3 -3
- package/dist/tui/services/ServiceManager.d.ts.map +1 -1
- package/dist/tui/services/ServiceManager.js +72 -58
- package/dist/tui/services/ServiceManager.js.map +1 -1
- package/dist/tui/services/SocketService.d.ts.map +1 -1
- package/dist/tui/services/SocketService.js +32 -32
- package/dist/tui/services/SocketService.js.map +1 -1
- package/dist/tui/services/ViteService.d.ts.map +1 -1
- package/dist/tui/services/ViteService.js +26 -28
- package/dist/tui/services/ViteService.js.map +1 -1
- package/dist/tui/services/index.d.ts +6 -6
- package/dist/tui/services/index.d.ts.map +1 -1
- package/dist/tui/services/index.js +6 -6
- package/dist/tui/services/index.js.map +1 -1
- package/mcp/gxp-api-server.js +83 -81
- package/package.json +109 -93
- package/runtime/PortalContainer.vue +258 -234
- package/runtime/dev-tools/DevToolsModal.vue +153 -155
- package/runtime/dev-tools/LayoutSwitcher.vue +144 -140
- package/runtime/dev-tools/MockDataEditor.vue +456 -433
- package/runtime/dev-tools/SocketSimulator.vue +379 -371
- package/runtime/dev-tools/StoreInspector.vue +517 -455
- package/runtime/dev-tools/index.js +5 -5
- package/runtime/fallback-layouts/PrivateLayout.vue +2 -2
- package/runtime/fallback-layouts/PublicLayout.vue +2 -2
- package/runtime/fallback-layouts/SystemLayout.vue +2 -2
- package/runtime/gxpStringsPlugin.js +159 -134
- package/runtime/index.html +17 -19
- package/runtime/main.js +24 -22
- package/runtime/mock-api/auth-middleware.js +15 -15
- package/runtime/mock-api/image-generator.js +46 -46
- package/runtime/mock-api/index.js +55 -55
- package/runtime/mock-api/response-generator.js +116 -105
- package/runtime/mock-api/route-generator.js +107 -84
- package/runtime/mock-api/socket-triggers.js +94 -93
- package/runtime/mock-api/spec-loader.js +79 -80
- package/runtime/package.json +3 -0
- package/runtime/server.js +68 -68
- package/runtime/stores/gxpPortalConfigStore.js +204 -186
- package/runtime/stores/index.js +2 -2
- package/runtime/vite-inspector-plugin.js +858 -707
- package/runtime/vite-source-tracker-plugin.js +132 -113
- package/runtime/vite.config.js +191 -139
- package/scripts/launch-chrome.js +41 -41
- package/scripts/pack-chrome.js +38 -39
- package/socket-events/AiSessionMessageCreated.json +17 -17
- package/socket-events/SocialStreamPostCreated.json +23 -23
- package/socket-events/SocialStreamPostVariantCompleted.json +22 -22
- package/template/.claude/agents/gxp-developer.md +100 -99
- package/template/.claude/settings.json +7 -7
- package/template/AGENTS.md +30 -23
- package/template/GEMINI.md +20 -20
- package/template/README.md +70 -53
- package/template/app-manifest.json +2 -4
- package/template/configuration.json +10 -10
- package/template/default-styling.css +1 -1
- package/template/index.html +18 -20
- package/template/main.js +24 -22
- package/template/src/DemoPage.vue +415 -362
- package/template/src/Plugin.vue +76 -85
- package/template/src/stores/index.js +3 -3
- package/template/src/stores/test-data.json +164 -172
- package/template/theme-layouts/AdditionalStyling.css +50 -50
- package/template/theme-layouts/PrivateLayout.vue +8 -12
- package/template/theme-layouts/PublicLayout.vue +8 -12
- package/template/theme-layouts/SystemLayout.vue +8 -12
- package/template/vite.extend.js +45 -0
- package/template/vite.config.js +0 -409
|
@@ -1,395 +1,425 @@
|
|
|
1
|
-
import fs from
|
|
2
|
-
import path from
|
|
3
|
-
import http from
|
|
4
|
-
import { fileURLToPath } from
|
|
5
|
-
import open from
|
|
6
|
-
import { EventEmitter } from
|
|
1
|
+
import fs from "fs"
|
|
2
|
+
import path from "path"
|
|
3
|
+
import http from "http"
|
|
4
|
+
import { fileURLToPath } from "url"
|
|
5
|
+
import open from "open"
|
|
6
|
+
import { EventEmitter } from "events"
|
|
7
7
|
|
|
8
8
|
// Gemini configuration interface
|
|
9
9
|
export interface GeminiConfig {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
systemPrompt?: string
|
|
11
|
+
includeDocs?: string[]
|
|
12
|
+
projectContext?: boolean
|
|
13
|
+
maxContextTokens?: number
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
// Auth tokens interface
|
|
17
17
|
interface AuthTokens {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
accessToken: string
|
|
19
|
+
refreshToken: string
|
|
20
|
+
expiresAt: number
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
// Get the gxdev config directory
|
|
24
24
|
function getConfigDir(): string {
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
const home = process.env.HOME || process.env.USERPROFILE || ""
|
|
26
|
+
return path.join(home, ".gxdev")
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
// Ensure config directory exists
|
|
30
30
|
function ensureConfigDir(): void {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
const configDir = getConfigDir()
|
|
32
|
+
if (!fs.existsSync(configDir)) {
|
|
33
|
+
fs.mkdirSync(configDir, { recursive: true })
|
|
34
|
+
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
// Get auth file path
|
|
38
38
|
function getAuthFilePath(): string {
|
|
39
|
-
|
|
39
|
+
return path.join(getConfigDir(), "gemini-auth.json")
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
// Get config file path
|
|
43
43
|
function getConfigFilePath(): string {
|
|
44
|
-
|
|
44
|
+
return path.join(getConfigDir(), "gemini-config.json")
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
// Get docs directory path
|
|
48
48
|
function getDocsDir(): string {
|
|
49
|
-
|
|
49
|
+
return path.join(getConfigDir(), "gemini-docs")
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
// Load saved auth tokens
|
|
53
53
|
export function loadAuthTokens(): AuthTokens | null {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
try {
|
|
55
|
+
const authPath = getAuthFilePath()
|
|
56
|
+
if (fs.existsSync(authPath)) {
|
|
57
|
+
const content = fs.readFileSync(authPath, "utf-8")
|
|
58
|
+
return JSON.parse(content)
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
// Invalid or missing auth file
|
|
62
|
+
}
|
|
63
|
+
return null
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
// Save auth tokens
|
|
67
67
|
function saveAuthTokens(tokens: AuthTokens): void {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
ensureConfigDir()
|
|
69
|
+
const authPath = getAuthFilePath()
|
|
70
|
+
fs.writeFileSync(authPath, JSON.stringify(tokens, null, 2))
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
// Clear auth tokens
|
|
74
74
|
export function clearAuthTokens(): void {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
const authPath = getAuthFilePath()
|
|
76
|
+
if (fs.existsSync(authPath)) {
|
|
77
|
+
fs.unlinkSync(authPath)
|
|
78
|
+
}
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
// Load Gemini config
|
|
82
82
|
export function loadGeminiConfig(): GeminiConfig {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
83
|
+
try {
|
|
84
|
+
const configPath = getConfigFilePath()
|
|
85
|
+
if (fs.existsSync(configPath)) {
|
|
86
|
+
const content = fs.readFileSync(configPath, "utf-8")
|
|
87
|
+
return JSON.parse(content)
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
// Invalid or missing config file
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
systemPrompt: "You are a helpful assistant for GxP plugin development.",
|
|
94
|
+
includeDocs: [],
|
|
95
|
+
projectContext: true,
|
|
96
|
+
maxContextTokens: 4000,
|
|
97
|
+
}
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
// Save Gemini config
|
|
101
101
|
export function saveGeminiConfig(config: GeminiConfig): void {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
ensureConfigDir()
|
|
103
|
+
const configPath = getConfigFilePath()
|
|
104
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2))
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
// Check if authenticated
|
|
108
108
|
export function isAuthenticated(): boolean {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
109
|
+
const tokens = loadAuthTokens()
|
|
110
|
+
if (!tokens) return false
|
|
111
|
+
// Check if token is expired (with 5 min buffer)
|
|
112
|
+
return tokens.expiresAt > Date.now() + 5 * 60 * 1000
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
// Gemini Service class
|
|
116
116
|
export class GeminiService extends EventEmitter {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
117
|
+
private conversationHistory: Array<{ role: string; content: string }> = []
|
|
118
|
+
private projectContext: string = ""
|
|
119
|
+
|
|
120
|
+
constructor() {
|
|
121
|
+
super()
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Start OAuth flow
|
|
125
|
+
async startOAuthFlow(): Promise<{ success: boolean; message: string }> {
|
|
126
|
+
return new Promise((resolve) => {
|
|
127
|
+
// Create local server for OAuth callback
|
|
128
|
+
const PORT = 8234
|
|
129
|
+
let server: http.Server
|
|
130
|
+
|
|
131
|
+
const handleCallback = (
|
|
132
|
+
req: http.IncomingMessage,
|
|
133
|
+
res: http.ServerResponse,
|
|
134
|
+
) => {
|
|
135
|
+
const url = new URL(req.url || "", `http://localhost:${PORT}`)
|
|
136
|
+
|
|
137
|
+
if (url.pathname === "/callback") {
|
|
138
|
+
const code = url.searchParams.get("code")
|
|
139
|
+
const error = url.searchParams.get("error")
|
|
140
|
+
|
|
141
|
+
if (error) {
|
|
142
|
+
res.writeHead(200, { "Content-Type": "text/html" })
|
|
143
|
+
res.end(
|
|
144
|
+
"<html><body><h1>Authentication Failed</h1><p>You can close this window.</p></body></html>",
|
|
145
|
+
)
|
|
146
|
+
server.close()
|
|
147
|
+
resolve({ success: false, message: `OAuth error: ${error}` })
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (code) {
|
|
152
|
+
// Exchange code for tokens
|
|
153
|
+
this.exchangeCodeForTokens(code)
|
|
154
|
+
.then((tokens) => {
|
|
155
|
+
saveAuthTokens(tokens)
|
|
156
|
+
res.writeHead(200, { "Content-Type": "text/html" })
|
|
157
|
+
res.end(
|
|
158
|
+
"<html><body><h1>Authentication Successful!</h1><p>You can close this window and return to gxdev.</p></body></html>",
|
|
159
|
+
)
|
|
160
|
+
server.close()
|
|
161
|
+
resolve({
|
|
162
|
+
success: true,
|
|
163
|
+
message: "Successfully authenticated with Google.",
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
.catch((err) => {
|
|
167
|
+
res.writeHead(200, { "Content-Type": "text/html" })
|
|
168
|
+
res.end(
|
|
169
|
+
"<html><body><h1>Authentication Failed</h1><p>Error exchanging code for tokens.</p></body></html>",
|
|
170
|
+
)
|
|
171
|
+
server.close()
|
|
172
|
+
resolve({
|
|
173
|
+
success: false,
|
|
174
|
+
message: `Token exchange error: ${err.message}`,
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
res.writeHead(404)
|
|
182
|
+
res.end("Not found")
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
server = http.createServer(handleCallback)
|
|
186
|
+
|
|
187
|
+
server.listen(PORT, () => {
|
|
188
|
+
// Construct OAuth URL
|
|
189
|
+
// Note: These are placeholder values - you'll need to set up actual Google Cloud credentials
|
|
190
|
+
const clientId = process.env.GOOGLE_CLIENT_ID || "YOUR_CLIENT_ID"
|
|
191
|
+
const redirectUri = `http://localhost:${PORT}/callback`
|
|
192
|
+
const scope = "https://www.googleapis.com/auth/generative-language"
|
|
193
|
+
|
|
194
|
+
const authUrl =
|
|
195
|
+
`https://accounts.google.com/o/oauth2/v2/auth?` +
|
|
196
|
+
`client_id=${encodeURIComponent(clientId)}` +
|
|
197
|
+
`&redirect_uri=${encodeURIComponent(redirectUri)}` +
|
|
198
|
+
`&response_type=code` +
|
|
199
|
+
`&scope=${encodeURIComponent(scope)}` +
|
|
200
|
+
`&access_type=offline` +
|
|
201
|
+
`&prompt=consent`
|
|
202
|
+
|
|
203
|
+
this.emit("log", `Opening browser for Google authentication...`)
|
|
204
|
+
this.emit("log", `If browser doesn't open, visit: ${authUrl}`)
|
|
205
|
+
|
|
206
|
+
open(authUrl).catch(() => {
|
|
207
|
+
this.emit(
|
|
208
|
+
"log",
|
|
209
|
+
`Could not open browser automatically. Please visit the URL above.`,
|
|
210
|
+
)
|
|
211
|
+
})
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
// Timeout after 5 minutes
|
|
215
|
+
setTimeout(
|
|
216
|
+
() => {
|
|
217
|
+
server.close()
|
|
218
|
+
resolve({
|
|
219
|
+
success: false,
|
|
220
|
+
message: "Authentication timed out. Please try again.",
|
|
221
|
+
})
|
|
222
|
+
},
|
|
223
|
+
5 * 60 * 1000,
|
|
224
|
+
)
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Exchange authorization code for tokens
|
|
229
|
+
private async exchangeCodeForTokens(code: string): Promise<AuthTokens> {
|
|
230
|
+
const clientId = process.env.GOOGLE_CLIENT_ID || "YOUR_CLIENT_ID"
|
|
231
|
+
const clientSecret =
|
|
232
|
+
process.env.GOOGLE_CLIENT_SECRET || "YOUR_CLIENT_SECRET"
|
|
233
|
+
const redirectUri = "http://localhost:8234/callback"
|
|
234
|
+
|
|
235
|
+
const response = await fetch("https://oauth2.googleapis.com/token", {
|
|
236
|
+
method: "POST",
|
|
237
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
238
|
+
body: new URLSearchParams({
|
|
239
|
+
code,
|
|
240
|
+
client_id: clientId,
|
|
241
|
+
client_secret: clientSecret,
|
|
242
|
+
redirect_uri: redirectUri,
|
|
243
|
+
grant_type: "authorization_code",
|
|
244
|
+
}),
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
if (!response.ok) {
|
|
248
|
+
throw new Error(`Token exchange failed: ${response.statusText}`)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const data = (await response.json()) as any
|
|
252
|
+
return {
|
|
253
|
+
accessToken: data.access_token,
|
|
254
|
+
refreshToken: data.refresh_token,
|
|
255
|
+
expiresAt: Date.now() + data.expires_in * 1000,
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Refresh access token
|
|
260
|
+
private async refreshAccessToken(): Promise<void> {
|
|
261
|
+
const tokens = loadAuthTokens()
|
|
262
|
+
if (!tokens?.refreshToken) {
|
|
263
|
+
throw new Error("No refresh token available")
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const clientId = process.env.GOOGLE_CLIENT_ID || "YOUR_CLIENT_ID"
|
|
267
|
+
const clientSecret =
|
|
268
|
+
process.env.GOOGLE_CLIENT_SECRET || "YOUR_CLIENT_SECRET"
|
|
269
|
+
|
|
270
|
+
const response = await fetch("https://oauth2.googleapis.com/token", {
|
|
271
|
+
method: "POST",
|
|
272
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
273
|
+
body: new URLSearchParams({
|
|
274
|
+
refresh_token: tokens.refreshToken,
|
|
275
|
+
client_id: clientId,
|
|
276
|
+
client_secret: clientSecret,
|
|
277
|
+
grant_type: "refresh_token",
|
|
278
|
+
}),
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
if (!response.ok) {
|
|
282
|
+
throw new Error("Failed to refresh token")
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const data = (await response.json()) as any
|
|
286
|
+
saveAuthTokens({
|
|
287
|
+
accessToken: data.access_token,
|
|
288
|
+
refreshToken: tokens.refreshToken,
|
|
289
|
+
expiresAt: Date.now() + data.expires_in * 1000,
|
|
290
|
+
})
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Get valid access token (refresh if needed)
|
|
294
|
+
private async getAccessToken(): Promise<string> {
|
|
295
|
+
const tokens = loadAuthTokens()
|
|
296
|
+
if (!tokens) {
|
|
297
|
+
throw new Error("Not authenticated. Run /gemini enable first.")
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Check if token needs refresh (with 5 min buffer)
|
|
301
|
+
if (tokens.expiresAt < Date.now() + 5 * 60 * 1000) {
|
|
302
|
+
await this.refreshAccessToken()
|
|
303
|
+
const newTokens = loadAuthTokens()
|
|
304
|
+
return newTokens!.accessToken
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return tokens.accessToken
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Load project context
|
|
311
|
+
loadProjectContext(cwd: string): void {
|
|
312
|
+
const files = ["CLAUDE.md", "README.md", "package.json"]
|
|
313
|
+
const contextParts: string[] = []
|
|
314
|
+
|
|
315
|
+
for (const file of files) {
|
|
316
|
+
const filePath = path.join(cwd, file)
|
|
317
|
+
if (fs.existsSync(filePath)) {
|
|
318
|
+
try {
|
|
319
|
+
const content = fs.readFileSync(filePath, "utf-8")
|
|
320
|
+
contextParts.push(`=== ${file} ===\n${content.slice(0, 2000)}`)
|
|
321
|
+
} catch {
|
|
322
|
+
// Skip unreadable files
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
this.projectContext = contextParts.join("\n\n")
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Load custom docs
|
|
331
|
+
loadCustomDocs(): string {
|
|
332
|
+
const docsDir = getDocsDir()
|
|
333
|
+
if (!fs.existsSync(docsDir)) {
|
|
334
|
+
return ""
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const docParts: string[] = []
|
|
338
|
+
try {
|
|
339
|
+
const files = fs.readdirSync(docsDir).filter((f) => f.endsWith(".md"))
|
|
340
|
+
for (const file of files.slice(0, 5)) {
|
|
341
|
+
// Limit to 5 docs
|
|
342
|
+
const content = fs.readFileSync(path.join(docsDir, file), "utf-8")
|
|
343
|
+
docParts.push(`=== ${file} ===\n${content.slice(0, 2000)}`)
|
|
344
|
+
}
|
|
345
|
+
} catch {
|
|
346
|
+
// Skip on error
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return docParts.join("\n\n")
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Send message to Gemini
|
|
353
|
+
async sendMessage(message: string): Promise<string> {
|
|
354
|
+
const config = loadGeminiConfig()
|
|
355
|
+
const accessToken = await this.getAccessToken()
|
|
356
|
+
|
|
357
|
+
// Build context
|
|
358
|
+
let systemContext = config.systemPrompt || ""
|
|
359
|
+
if (config.projectContext && this.projectContext) {
|
|
360
|
+
systemContext += "\n\nProject Context:\n" + this.projectContext
|
|
361
|
+
}
|
|
362
|
+
const customDocs = this.loadCustomDocs()
|
|
363
|
+
if (customDocs) {
|
|
364
|
+
systemContext += "\n\nDocumentation:\n" + customDocs
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Add to conversation history
|
|
368
|
+
this.conversationHistory.push({ role: "user", content: message })
|
|
369
|
+
|
|
370
|
+
// Prepare request body for Gemini API
|
|
371
|
+
const requestBody = {
|
|
372
|
+
contents: [
|
|
373
|
+
{
|
|
374
|
+
role: "user",
|
|
375
|
+
parts: [{ text: systemContext + "\n\nUser: " + message }],
|
|
376
|
+
},
|
|
377
|
+
],
|
|
378
|
+
generationConfig: {
|
|
379
|
+
maxOutputTokens: 2048,
|
|
380
|
+
temperature: 0.7,
|
|
381
|
+
},
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Call Gemini API
|
|
385
|
+
const response = await fetch(
|
|
386
|
+
"https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent",
|
|
387
|
+
{
|
|
388
|
+
method: "POST",
|
|
389
|
+
headers: {
|
|
390
|
+
Authorization: `Bearer ${accessToken}`,
|
|
391
|
+
"Content-Type": "application/json",
|
|
392
|
+
},
|
|
393
|
+
body: JSON.stringify(requestBody),
|
|
394
|
+
},
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
if (!response.ok) {
|
|
398
|
+
const errorText = await response.text()
|
|
399
|
+
throw new Error(`Gemini API error: ${response.status} - ${errorText}`)
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const data = (await response.json()) as any
|
|
403
|
+
const responseText =
|
|
404
|
+
data.candidates?.[0]?.content?.parts?.[0]?.text ||
|
|
405
|
+
"No response generated."
|
|
406
|
+
|
|
407
|
+
// Add to conversation history
|
|
408
|
+
this.conversationHistory.push({ role: "assistant", content: responseText })
|
|
409
|
+
|
|
410
|
+
return responseText
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Clear conversation history
|
|
414
|
+
clearConversation(): void {
|
|
415
|
+
this.conversationHistory = []
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Get conversation history
|
|
419
|
+
getConversationHistory(): Array<{ role: string; content: string }> {
|
|
420
|
+
return [...this.conversationHistory]
|
|
421
|
+
}
|
|
392
422
|
}
|
|
393
423
|
|
|
394
424
|
// Singleton instance
|
|
395
|
-
export const geminiService = new GeminiService()
|
|
425
|
+
export const geminiService = new GeminiService()
|