@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,509 +1,565 @@
|
|
|
1
|
-
import fs from
|
|
2
|
-
import path from
|
|
3
|
-
import { spawn, execSync } from
|
|
4
|
-
import { EventEmitter } from
|
|
1
|
+
import fs from "fs"
|
|
2
|
+
import path from "path"
|
|
3
|
+
import { spawn, execSync } from "child_process"
|
|
4
|
+
import { EventEmitter } from "events"
|
|
5
5
|
|
|
6
6
|
// AI Provider types
|
|
7
|
-
export type AIProvider =
|
|
7
|
+
export type AIProvider = "claude" | "codex" | "gemini"
|
|
8
8
|
|
|
9
9
|
export interface AIProviderInfo {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
id: AIProvider
|
|
11
|
+
name: string
|
|
12
|
+
available: boolean
|
|
13
|
+
method?: string // For gemini: 'cli', 'api_key', 'gcloud'
|
|
14
|
+
reason?: string
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
// AI Configuration
|
|
18
18
|
export interface AIConfig {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
provider: AIProvider
|
|
20
|
+
systemPrompt: string
|
|
21
|
+
projectContext: boolean
|
|
22
|
+
maxContextTokens: number
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
// Get the gxdev config directory
|
|
26
26
|
function getConfigDir(): string {
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
const home = process.env.HOME || process.env.USERPROFILE || ""
|
|
28
|
+
return path.join(home, ".gxdev")
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
// Ensure config directory exists
|
|
32
32
|
function ensureConfigDir(): void {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
const configDir = getConfigDir()
|
|
34
|
+
if (!fs.existsSync(configDir)) {
|
|
35
|
+
fs.mkdirSync(configDir, { recursive: true })
|
|
36
|
+
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
// Get AI config file path
|
|
40
40
|
function getAIConfigPath(): string {
|
|
41
|
-
|
|
41
|
+
return path.join(getConfigDir(), "ai-config.json")
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
// Load AI config
|
|
45
45
|
export function loadAIConfig(): AIConfig {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
46
|
+
try {
|
|
47
|
+
const configPath = getAIConfigPath()
|
|
48
|
+
if (fs.existsSync(configPath)) {
|
|
49
|
+
const content = fs.readFileSync(configPath, "utf-8")
|
|
50
|
+
return JSON.parse(content)
|
|
51
|
+
}
|
|
52
|
+
} catch {
|
|
53
|
+
// Invalid or missing config file
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
provider: "claude", // Default to Claude
|
|
57
|
+
systemPrompt:
|
|
58
|
+
"You are a helpful assistant for GxP plugin development. Help the user build Vue.js components for the GxP kiosk platform.",
|
|
59
|
+
projectContext: true,
|
|
60
|
+
maxContextTokens: 4000,
|
|
61
|
+
}
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
// Save AI config
|
|
64
65
|
export function saveAIConfig(config: AIConfig): void {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
ensureConfigDir()
|
|
67
|
+
const configPath = getAIConfigPath()
|
|
68
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2))
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
// Check if a command exists
|
|
71
72
|
function commandExists(cmd: string): boolean {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
73
|
+
try {
|
|
74
|
+
execSync(`which ${cmd}`, { stdio: "pipe" })
|
|
75
|
+
return true
|
|
76
|
+
} catch {
|
|
77
|
+
return false
|
|
78
|
+
}
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
// Check available AI providers
|
|
81
82
|
export function getAvailableProviders(): AIProviderInfo[] {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
83
|
+
const providers: AIProviderInfo[] = []
|
|
84
|
+
|
|
85
|
+
// Check Claude CLI
|
|
86
|
+
if (commandExists("claude")) {
|
|
87
|
+
providers.push({
|
|
88
|
+
id: "claude",
|
|
89
|
+
name: "Claude",
|
|
90
|
+
available: true,
|
|
91
|
+
})
|
|
92
|
+
} else {
|
|
93
|
+
providers.push({
|
|
94
|
+
id: "claude",
|
|
95
|
+
name: "Claude",
|
|
96
|
+
available: false,
|
|
97
|
+
reason: "Install: npm i -g @anthropic-ai/claude-code && claude login",
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Check Codex CLI
|
|
102
|
+
if (commandExists("codex")) {
|
|
103
|
+
providers.push({
|
|
104
|
+
id: "codex",
|
|
105
|
+
name: "Codex",
|
|
106
|
+
available: true,
|
|
107
|
+
})
|
|
108
|
+
} else {
|
|
109
|
+
providers.push({
|
|
110
|
+
id: "codex",
|
|
111
|
+
name: "Codex",
|
|
112
|
+
available: false,
|
|
113
|
+
reason: "Install: npm i -g @openai/codex && codex auth",
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Check Gemini (CLI, API key, or gcloud)
|
|
118
|
+
if (commandExists("gemini")) {
|
|
119
|
+
providers.push({
|
|
120
|
+
id: "gemini",
|
|
121
|
+
name: "Gemini",
|
|
122
|
+
available: true,
|
|
123
|
+
method: "cli",
|
|
124
|
+
})
|
|
125
|
+
} else if (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY) {
|
|
126
|
+
providers.push({
|
|
127
|
+
id: "gemini",
|
|
128
|
+
name: "Gemini",
|
|
129
|
+
available: true,
|
|
130
|
+
method: "api_key",
|
|
131
|
+
})
|
|
132
|
+
} else if (commandExists("gcloud")) {
|
|
133
|
+
try {
|
|
134
|
+
const authList = execSync("gcloud auth list --format='value(account)'", {
|
|
135
|
+
stdio: "pipe",
|
|
136
|
+
}).toString()
|
|
137
|
+
if (authList.trim()) {
|
|
138
|
+
providers.push({
|
|
139
|
+
id: "gemini",
|
|
140
|
+
name: "Gemini",
|
|
141
|
+
available: true,
|
|
142
|
+
method: "gcloud",
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
} catch {
|
|
146
|
+
providers.push({
|
|
147
|
+
id: "gemini",
|
|
148
|
+
name: "Gemini",
|
|
149
|
+
available: false,
|
|
150
|
+
reason: "Install: npm i -g @google/gemini-cli && gemini",
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
providers.push({
|
|
155
|
+
id: "gemini",
|
|
156
|
+
name: "Gemini",
|
|
157
|
+
available: false,
|
|
158
|
+
reason: "Install: npm i -g @google/gemini-cli && gemini",
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return providers
|
|
160
163
|
}
|
|
161
164
|
|
|
162
165
|
// Get provider display name with status
|
|
163
166
|
export function getProviderStatus(provider: AIProviderInfo): string {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
167
|
+
if (!provider.available) {
|
|
168
|
+
return `${provider.name} (not available)`
|
|
169
|
+
}
|
|
170
|
+
if (provider.method) {
|
|
171
|
+
switch (provider.method) {
|
|
172
|
+
case "cli":
|
|
173
|
+
return `${provider.name} (CLI)`
|
|
174
|
+
case "api_key":
|
|
175
|
+
return `${provider.name} (API key)`
|
|
176
|
+
case "gcloud":
|
|
177
|
+
return `${provider.name} (gcloud)`
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return `${provider.name} (logged in)`
|
|
178
181
|
}
|
|
179
182
|
|
|
180
183
|
// AI Service class
|
|
181
184
|
export class AIService extends EventEmitter {
|
|
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
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
185
|
+
private conversationHistory: Array<{ role: string; content: string }> = []
|
|
186
|
+
private projectContext: string = ""
|
|
187
|
+
private currentProvider: AIProvider
|
|
188
|
+
private geminiMethod?: string
|
|
189
|
+
|
|
190
|
+
constructor() {
|
|
191
|
+
super()
|
|
192
|
+
const config = loadAIConfig()
|
|
193
|
+
this.currentProvider = config.provider
|
|
194
|
+
|
|
195
|
+
// Determine gemini method if that's the current provider
|
|
196
|
+
const providers = getAvailableProviders()
|
|
197
|
+
const geminiProvider = providers.find((p) => p.id === "gemini")
|
|
198
|
+
if (geminiProvider?.available) {
|
|
199
|
+
this.geminiMethod = geminiProvider.method
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Get current provider
|
|
204
|
+
getProvider(): AIProvider {
|
|
205
|
+
return this.currentProvider
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Set current provider
|
|
209
|
+
setProvider(provider: AIProvider): { success: boolean; message: string } {
|
|
210
|
+
const providers = getAvailableProviders()
|
|
211
|
+
const providerInfo = providers.find((p) => p.id === provider)
|
|
212
|
+
|
|
213
|
+
if (!providerInfo) {
|
|
214
|
+
return { success: false, message: `Unknown provider: ${provider}` }
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (!providerInfo.available) {
|
|
218
|
+
return {
|
|
219
|
+
success: false,
|
|
220
|
+
message: `${providerInfo.name} is not available. ${providerInfo.reason || ""}`,
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
this.currentProvider = provider
|
|
225
|
+
if (provider === "gemini") {
|
|
226
|
+
this.geminiMethod = providerInfo.method
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Save to config
|
|
230
|
+
const config = loadAIConfig()
|
|
231
|
+
config.provider = provider
|
|
232
|
+
saveAIConfig(config)
|
|
233
|
+
|
|
234
|
+
this.clearConversation()
|
|
235
|
+
return { success: true, message: `Switched to ${providerInfo.name}` }
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Check if current provider is available
|
|
239
|
+
isAvailable(): boolean {
|
|
240
|
+
const providers = getAvailableProviders()
|
|
241
|
+
const current = providers.find((p) => p.id === this.currentProvider)
|
|
242
|
+
return current?.available || false
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Get provider info
|
|
246
|
+
getProviderInfo(): AIProviderInfo | undefined {
|
|
247
|
+
const providers = getAvailableProviders()
|
|
248
|
+
return providers.find((p) => p.id === this.currentProvider)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Load project context
|
|
252
|
+
loadProjectContext(cwd: string): void {
|
|
253
|
+
const files = [
|
|
254
|
+
"CLAUDE.md",
|
|
255
|
+
"AGENTS.md",
|
|
256
|
+
"GEMINI.md",
|
|
257
|
+
"README.md",
|
|
258
|
+
"package.json",
|
|
259
|
+
"app-manifest.json",
|
|
260
|
+
]
|
|
261
|
+
const contextParts: string[] = []
|
|
262
|
+
|
|
263
|
+
for (const file of files) {
|
|
264
|
+
const filePath = path.join(cwd, file)
|
|
265
|
+
if (fs.existsSync(filePath)) {
|
|
266
|
+
try {
|
|
267
|
+
const content = fs.readFileSync(filePath, "utf-8")
|
|
268
|
+
// Limit each file to 2000 chars
|
|
269
|
+
contextParts.push(`=== ${file} ===\n${content.slice(0, 2000)}`)
|
|
270
|
+
} catch {
|
|
271
|
+
// Skip unreadable files
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
this.projectContext = contextParts.join("\n\n")
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Send message using current provider
|
|
280
|
+
async sendMessage(message: string): Promise<string> {
|
|
281
|
+
const config = loadAIConfig()
|
|
282
|
+
|
|
283
|
+
// Build context
|
|
284
|
+
let systemContext = config.systemPrompt || ""
|
|
285
|
+
if (config.projectContext && this.projectContext) {
|
|
286
|
+
systemContext += "\n\nProject Context:\n" + this.projectContext
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
switch (this.currentProvider) {
|
|
290
|
+
case "claude":
|
|
291
|
+
return this.sendWithClaude(message, systemContext)
|
|
292
|
+
case "codex":
|
|
293
|
+
return this.sendWithCodex(message, systemContext)
|
|
294
|
+
case "gemini":
|
|
295
|
+
return this.sendWithGemini(message, systemContext)
|
|
296
|
+
default:
|
|
297
|
+
throw new Error(`Unknown provider: ${this.currentProvider}`)
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Send message with Claude CLI
|
|
302
|
+
private async sendWithClaude(
|
|
303
|
+
message: string,
|
|
304
|
+
systemContext: string,
|
|
305
|
+
): Promise<string> {
|
|
306
|
+
return new Promise((resolve, reject) => {
|
|
307
|
+
let output = ""
|
|
308
|
+
let errorOutput = ""
|
|
309
|
+
|
|
310
|
+
const fullPrompt = systemContext
|
|
311
|
+
? `${systemContext}\n\nUser: ${message}`
|
|
312
|
+
: message
|
|
313
|
+
|
|
314
|
+
const claude = spawn("claude", ["--print", "-p", fullPrompt], {
|
|
315
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
316
|
+
shell: true,
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
claude.stdout.on("data", (data) => {
|
|
320
|
+
output += data.toString()
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
claude.stderr.on("data", (data) => {
|
|
324
|
+
errorOutput += data.toString()
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
claude.on("close", (code) => {
|
|
328
|
+
if (code !== 0) {
|
|
329
|
+
reject(new Error(`Claude error: ${errorOutput || "Unknown error"}`))
|
|
330
|
+
return
|
|
331
|
+
}
|
|
332
|
+
this.conversationHistory.push({ role: "user", content: message })
|
|
333
|
+
this.conversationHistory.push({ role: "assistant", content: output })
|
|
334
|
+
resolve(output.trim())
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
claude.on("error", (err) => {
|
|
338
|
+
reject(new Error(`Failed to run Claude: ${err.message}`))
|
|
339
|
+
})
|
|
340
|
+
})
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Send message with Codex CLI
|
|
344
|
+
private async sendWithCodex(
|
|
345
|
+
message: string,
|
|
346
|
+
systemContext: string,
|
|
347
|
+
): Promise<string> {
|
|
348
|
+
return new Promise((resolve, reject) => {
|
|
349
|
+
let output = ""
|
|
350
|
+
let errorOutput = ""
|
|
351
|
+
|
|
352
|
+
const fullPrompt = systemContext
|
|
353
|
+
? `${systemContext}\n\nUser: ${message}`
|
|
354
|
+
: message
|
|
355
|
+
|
|
356
|
+
const codex = spawn("codex", ["--quiet", "-p", fullPrompt], {
|
|
357
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
358
|
+
shell: true,
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
codex.stdout.on("data", (data) => {
|
|
362
|
+
output += data.toString()
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
codex.stderr.on("data", (data) => {
|
|
366
|
+
errorOutput += data.toString()
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
codex.on("close", (code) => {
|
|
370
|
+
if (code !== 0) {
|
|
371
|
+
reject(new Error(`Codex error: ${errorOutput || "Unknown error"}`))
|
|
372
|
+
return
|
|
373
|
+
}
|
|
374
|
+
this.conversationHistory.push({ role: "user", content: message })
|
|
375
|
+
this.conversationHistory.push({ role: "assistant", content: output })
|
|
376
|
+
resolve(output.trim())
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
codex.on("error", (err) => {
|
|
380
|
+
reject(new Error(`Failed to run Codex: ${err.message}`))
|
|
381
|
+
})
|
|
382
|
+
})
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Send message with Gemini
|
|
386
|
+
private async sendWithGemini(
|
|
387
|
+
message: string,
|
|
388
|
+
systemContext: string,
|
|
389
|
+
): Promise<string> {
|
|
390
|
+
const fullPrompt = systemContext
|
|
391
|
+
? `${systemContext}\n\nUser: ${message}`
|
|
392
|
+
: message
|
|
393
|
+
|
|
394
|
+
if (this.geminiMethod === "cli") {
|
|
395
|
+
return this.sendWithGeminiCli(fullPrompt)
|
|
396
|
+
} else if (this.geminiMethod === "api_key") {
|
|
397
|
+
return this.sendWithGeminiApi(fullPrompt)
|
|
398
|
+
} else if (this.geminiMethod === "gcloud") {
|
|
399
|
+
return this.sendWithGeminiGcloud(fullPrompt)
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
throw new Error("Gemini is not properly configured")
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Send with Gemini CLI
|
|
406
|
+
private async sendWithGeminiCli(prompt: string): Promise<string> {
|
|
407
|
+
return new Promise((resolve, reject) => {
|
|
408
|
+
let output = ""
|
|
409
|
+
let errorOutput = ""
|
|
410
|
+
|
|
411
|
+
const gemini = spawn("gemini", ["-p", prompt], {
|
|
412
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
413
|
+
shell: true,
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
gemini.stdout.on("data", (data) => {
|
|
417
|
+
output += data.toString()
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
gemini.stderr.on("data", (data) => {
|
|
421
|
+
errorOutput += data.toString()
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
gemini.on("close", (code) => {
|
|
425
|
+
if (code !== 0) {
|
|
426
|
+
reject(new Error(`Gemini error: ${errorOutput || "Unknown error"}`))
|
|
427
|
+
return
|
|
428
|
+
}
|
|
429
|
+
this.conversationHistory.push({ role: "user", content: prompt })
|
|
430
|
+
this.conversationHistory.push({ role: "assistant", content: output })
|
|
431
|
+
resolve(output.trim())
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
gemini.on("error", (err) => {
|
|
435
|
+
reject(new Error(`Failed to run Gemini: ${err.message}`))
|
|
436
|
+
})
|
|
437
|
+
})
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Send with Gemini API
|
|
441
|
+
private async sendWithGeminiApi(prompt: string): Promise<string> {
|
|
442
|
+
const apiKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY
|
|
443
|
+
if (!apiKey) {
|
|
444
|
+
throw new Error("GEMINI_API_KEY not set")
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const response = await fetch(
|
|
448
|
+
`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`,
|
|
449
|
+
{
|
|
450
|
+
method: "POST",
|
|
451
|
+
headers: { "Content-Type": "application/json" },
|
|
452
|
+
body: JSON.stringify({
|
|
453
|
+
contents: [{ role: "user", parts: [{ text: prompt }] }],
|
|
454
|
+
generationConfig: { maxOutputTokens: 2048, temperature: 0.7 },
|
|
455
|
+
}),
|
|
456
|
+
},
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
if (!response.ok) {
|
|
460
|
+
const errorText = await response.text()
|
|
461
|
+
throw new Error(`Gemini API error: ${response.status} - ${errorText}`)
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const data = (await response.json()) as any
|
|
465
|
+
const responseText =
|
|
466
|
+
data.candidates?.[0]?.content?.parts?.[0]?.text ||
|
|
467
|
+
"No response generated."
|
|
468
|
+
|
|
469
|
+
this.conversationHistory.push({ role: "user", content: prompt })
|
|
470
|
+
this.conversationHistory.push({ role: "assistant", content: responseText })
|
|
471
|
+
|
|
472
|
+
return responseText
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Send with Gemini via gcloud
|
|
476
|
+
private async sendWithGeminiGcloud(prompt: string): Promise<string> {
|
|
477
|
+
return new Promise((resolve, reject) => {
|
|
478
|
+
let accessToken: string
|
|
479
|
+
let projectId: string
|
|
480
|
+
|
|
481
|
+
try {
|
|
482
|
+
accessToken = execSync("gcloud auth print-access-token", {
|
|
483
|
+
stdio: "pipe",
|
|
484
|
+
})
|
|
485
|
+
.toString()
|
|
486
|
+
.trim()
|
|
487
|
+
projectId = execSync("gcloud config get-value project", {
|
|
488
|
+
stdio: "pipe",
|
|
489
|
+
})
|
|
490
|
+
.toString()
|
|
491
|
+
.trim()
|
|
492
|
+
} catch (error) {
|
|
493
|
+
reject(new Error("Failed to get gcloud credentials"))
|
|
494
|
+
return
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const requestBody = JSON.stringify({
|
|
498
|
+
contents: [{ role: "user", parts: [{ text: prompt }] }],
|
|
499
|
+
generationConfig: { maxOutputTokens: 2048, temperature: 0.7 },
|
|
500
|
+
})
|
|
501
|
+
|
|
502
|
+
const curl = spawn(
|
|
503
|
+
"curl",
|
|
504
|
+
[
|
|
505
|
+
"-s",
|
|
506
|
+
"-X",
|
|
507
|
+
"POST",
|
|
508
|
+
`https://us-central1-aiplatform.googleapis.com/v1/projects/${projectId}/locations/us-central1/publishers/google/models/gemini-1.5-flash:generateContent`,
|
|
509
|
+
"-H",
|
|
510
|
+
`Authorization: Bearer ${accessToken}`,
|
|
511
|
+
"-H",
|
|
512
|
+
"Content-Type: application/json",
|
|
513
|
+
"-d",
|
|
514
|
+
requestBody,
|
|
515
|
+
],
|
|
516
|
+
{ stdio: ["pipe", "pipe", "pipe"] },
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
let output = ""
|
|
520
|
+
let errorOutput = ""
|
|
521
|
+
|
|
522
|
+
curl.stdout.on("data", (data) => {
|
|
523
|
+
output += data.toString()
|
|
524
|
+
})
|
|
525
|
+
curl.stderr.on("data", (data) => {
|
|
526
|
+
errorOutput += data.toString()
|
|
527
|
+
})
|
|
528
|
+
|
|
529
|
+
curl.on("close", (code) => {
|
|
530
|
+
if (code !== 0) {
|
|
531
|
+
reject(new Error(`Gemini gcloud error: ${errorOutput}`))
|
|
532
|
+
return
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
try {
|
|
536
|
+
const data = JSON.parse(output)
|
|
537
|
+
const responseText =
|
|
538
|
+
data.candidates?.[0]?.content?.parts?.[0]?.text ||
|
|
539
|
+
"No response generated."
|
|
540
|
+
this.conversationHistory.push({ role: "user", content: prompt })
|
|
541
|
+
this.conversationHistory.push({
|
|
542
|
+
role: "assistant",
|
|
543
|
+
content: responseText,
|
|
544
|
+
})
|
|
545
|
+
resolve(responseText)
|
|
546
|
+
} catch (parseError) {
|
|
547
|
+
reject(new Error(`Failed to parse Gemini response`))
|
|
548
|
+
}
|
|
549
|
+
})
|
|
550
|
+
})
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Clear conversation history
|
|
554
|
+
clearConversation(): void {
|
|
555
|
+
this.conversationHistory = []
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Get conversation history
|
|
559
|
+
getConversationHistory(): Array<{ role: string; content: string }> {
|
|
560
|
+
return [...this.conversationHistory]
|
|
561
|
+
}
|
|
506
562
|
}
|
|
507
563
|
|
|
508
564
|
// Singleton instance
|
|
509
|
-
export const aiService = new AIService()
|
|
565
|
+
export const aiService = new AIService()
|