ai-changelog-generator-extension 0.4.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/.vscode/launch.json +41 -0
- package/.vscode/settings.json +15 -0
- package/.vscode/tasks.json +26 -0
- package/.vscodeignore +62 -0
- package/ARCHITECTURE-v0.4.0.md +418 -0
- package/CHANGELOG.md +28 -0
- package/DEVELOPMENT.md +266 -0
- package/IMPLEMENTATION_SUMMARY.md +240 -0
- package/IMPLEMENTATION_SUMMARY_v0.4.0.md +239 -0
- package/LICENSE +21 -0
- package/MIGRATION_CHECKLIST.md +200 -0
- package/QUICK-REFERENCE-v0.4.0.md +251 -0
- package/QUICK-START.md +211 -0
- package/README.md +95 -0
- package/RELEASE-NOTES-0.2.0.md +176 -0
- package/RELEASE-NOTES-0.3.0.md +275 -0
- package/RELEASE-NOTES-0.4.0.md +194 -0
- package/SETTINGS-GUIDE.md +418 -0
- package/TESTING-v0.4.0.md +263 -0
- package/TESTING.md +255 -0
- package/esbuild.js +54 -0
- package/package.json +282 -0
- package/setup.sh +58 -0
- package/src/commands/configureProvider.ts +28 -0
- package/src/commands/setupWizard.ts +333 -0
- package/src/commands/testConnection.ts +93 -0
- package/src/extension.ts +264 -0
- package/src/services/EnvFileService.ts +172 -0
- package/src/services/ExtensionConfigurationManager.ts +224 -0
- package/src/services/StatusService.ts +211 -0
- package/src/types/core.d.ts +85 -0
- package/src/views/CommitSidebarProvider.ts +572 -0
- package/src/views/OnboardingPanelProvider.ts +1366 -0
- package/src/views/ReleaseSidebarProvider.ts +370 -0
- package/src/views/SettingsPanelProvider.ts +991 -0
- package/test-package.sh +38 -0
- package/tsconfig.json +18 -0
- package/v0.4.0-COMPLETE.md +349 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import * as vscode from 'vscode'
|
|
2
|
+
import * as fs from 'fs'
|
|
3
|
+
import * as path from 'path'
|
|
4
|
+
|
|
5
|
+
export interface DetectedEnvVars {
|
|
6
|
+
[key: string]: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class EnvFileService {
|
|
10
|
+
/**
|
|
11
|
+
* Detect .env files in workspace and parse compatible API keys
|
|
12
|
+
*/
|
|
13
|
+
static async detectEnvironmentVariables(): Promise<DetectedEnvVars> {
|
|
14
|
+
const workspaceFolders = vscode.workspace.workspaceFolders
|
|
15
|
+
if (!workspaceFolders || workspaceFolders.length === 0) {
|
|
16
|
+
return {}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const rootPath = workspaceFolders[0].uri.fsPath
|
|
20
|
+
const config = vscode.workspace.getConfiguration('aiChangelog')
|
|
21
|
+
const envFilePath = config.get<string>('envFilePath', '.env.local')
|
|
22
|
+
|
|
23
|
+
// Try multiple .env file locations
|
|
24
|
+
const envFiles = [
|
|
25
|
+
path.join(rootPath, envFilePath),
|
|
26
|
+
path.join(rootPath, '.env.local'),
|
|
27
|
+
path.join(rootPath, '.env'),
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
let detectedVars: DetectedEnvVars = {}
|
|
31
|
+
|
|
32
|
+
for (const envFile of envFiles) {
|
|
33
|
+
if (fs.existsSync(envFile)) {
|
|
34
|
+
try {
|
|
35
|
+
const content = fs.readFileSync(envFile, 'utf8')
|
|
36
|
+
const vars = this.parseEnvFile(content)
|
|
37
|
+
detectedVars = { ...detectedVars, ...vars }
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error(`Failed to read ${envFile}:`, error)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return detectedVars
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Parse .env file content and extract AI provider keys
|
|
49
|
+
*/
|
|
50
|
+
private static parseEnvFile(content: string): DetectedEnvVars {
|
|
51
|
+
const lines = content.split('\n')
|
|
52
|
+
const vars: DetectedEnvVars = {}
|
|
53
|
+
|
|
54
|
+
// Known API key and config patterns
|
|
55
|
+
const keyPatterns = [
|
|
56
|
+
'AI_PROVIDER',
|
|
57
|
+
'OPENAI_API_KEY',
|
|
58
|
+
'ANTHROPIC_API_KEY',
|
|
59
|
+
'GOOGLE_API_KEY',
|
|
60
|
+
'AZURE_OPENAI_KEY',
|
|
61
|
+
'AZURE_OPENAI_API_KEY',
|
|
62
|
+
'AZURE_OPENAI_ENDPOINT',
|
|
63
|
+
'AZURE_OPENAI_DEPLOYMENT_NAME',
|
|
64
|
+
'AZURE_OPENAI_API_VERSION',
|
|
65
|
+
'AZURE_OPENAI_USE_V1_API',
|
|
66
|
+
'AZURE_USE_AD_AUTH',
|
|
67
|
+
'AWS_ACCESS_KEY_ID',
|
|
68
|
+
'AWS_SECRET_ACCESS_KEY',
|
|
69
|
+
'HUGGINGFACE_API_KEY',
|
|
70
|
+
'OLLAMA_HOST',
|
|
71
|
+
'OLLAMA_MODEL',
|
|
72
|
+
'LMSTUDIO_BASE_URL',
|
|
73
|
+
'VERTEX_PROJECT_ID',
|
|
74
|
+
'VERTEX_LOCATION',
|
|
75
|
+
'GOOGLE_APPLICATION_CREDENTIALS',
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
for (const line of lines) {
|
|
79
|
+
const trimmed = line.trim()
|
|
80
|
+
|
|
81
|
+
// Skip comments and empty lines
|
|
82
|
+
if (trimmed.startsWith('#') || !trimmed || !trimmed.includes('=')) {
|
|
83
|
+
continue
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const [key, ...valueParts] = trimmed.split('=')
|
|
87
|
+
const value = valueParts.join('=').trim()
|
|
88
|
+
|
|
89
|
+
// Remove quotes
|
|
90
|
+
const cleanValue = value.replace(/^["']|["']$/g, '')
|
|
91
|
+
|
|
92
|
+
// Check if this is a known API key
|
|
93
|
+
if (keyPatterns.includes(key.trim())) {
|
|
94
|
+
vars[key.trim()] = cleanValue
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return vars
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Save API key to .env file
|
|
103
|
+
*/
|
|
104
|
+
static async saveToEnvFile(key: string, value: string): Promise<void> {
|
|
105
|
+
const workspaceFolders = vscode.workspace.workspaceFolders
|
|
106
|
+
if (!workspaceFolders || workspaceFolders.length === 0) {
|
|
107
|
+
throw new Error('No workspace folder open')
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const rootPath = workspaceFolders[0].uri.fsPath
|
|
111
|
+
const config = vscode.workspace.getConfiguration('aiChangelog')
|
|
112
|
+
const envFilePath = config.get<string>('envFilePath', '.env.local')
|
|
113
|
+
const envFile = path.join(rootPath, envFilePath)
|
|
114
|
+
|
|
115
|
+
let content = ''
|
|
116
|
+
let keyExists = false
|
|
117
|
+
|
|
118
|
+
// Read existing content
|
|
119
|
+
if (fs.existsSync(envFile)) {
|
|
120
|
+
content = fs.readFileSync(envFile, 'utf8')
|
|
121
|
+
const lines = content.split('\n')
|
|
122
|
+
const updatedLines: string[] = []
|
|
123
|
+
|
|
124
|
+
for (const line of lines) {
|
|
125
|
+
if (line.trim().startsWith(`${key}=`)) {
|
|
126
|
+
updatedLines.push(`${key}="${value}"`)
|
|
127
|
+
keyExists = true
|
|
128
|
+
} else {
|
|
129
|
+
updatedLines.push(line)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
content = updatedLines.join('\n')
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Add key if it doesn't exist
|
|
137
|
+
if (!keyExists) {
|
|
138
|
+
if (content && !content.endsWith('\n')) {
|
|
139
|
+
content += '\n'
|
|
140
|
+
}
|
|
141
|
+
content += `${key}="${value}"\n`
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Write to file
|
|
145
|
+
fs.writeFileSync(envFile, content, 'utf8')
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Check if any environment variables are detected
|
|
150
|
+
*/
|
|
151
|
+
static async hasEnvVars(): Promise<boolean> {
|
|
152
|
+
const vars = await this.detectEnvironmentVariables()
|
|
153
|
+
return Object.keys(vars).length > 0
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Get provider name from key name
|
|
158
|
+
*/
|
|
159
|
+
static getProviderFromKey(key: string): string | null {
|
|
160
|
+
const keyMap: Record<string, string> = {
|
|
161
|
+
OPENAI_API_KEY: 'openai',
|
|
162
|
+
ANTHROPIC_API_KEY: 'anthropic',
|
|
163
|
+
GOOGLE_API_KEY: 'google',
|
|
164
|
+
AZURE_OPENAI_KEY: 'azure',
|
|
165
|
+
AZURE_OPENAI_API_KEY: 'azure',
|
|
166
|
+
AWS_ACCESS_KEY_ID: 'bedrock',
|
|
167
|
+
HUGGINGFACE_API_KEY: 'huggingface',
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return keyMap[key] || null
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import * as vscode from 'vscode'
|
|
2
|
+
import { EnvFileService } from './EnvFileService'
|
|
3
|
+
|
|
4
|
+
export class ExtensionConfigurationManager {
|
|
5
|
+
private secretStorage: vscode.SecretStorage
|
|
6
|
+
private initialized = false
|
|
7
|
+
private config: Record<string, any> = {}
|
|
8
|
+
|
|
9
|
+
constructor(secretStorage: vscode.SecretStorage) {
|
|
10
|
+
this.secretStorage = secretStorage
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async initialize(): Promise<void> {
|
|
14
|
+
if (this.initialized) return
|
|
15
|
+
|
|
16
|
+
// Load base config
|
|
17
|
+
this.loadConfig()
|
|
18
|
+
|
|
19
|
+
const wsConfig = vscode.workspace.getConfiguration('aiChangelog')
|
|
20
|
+
const storageMode = wsConfig.get<string>('storageMode', 'secrets')
|
|
21
|
+
|
|
22
|
+
if (storageMode === 'env') {
|
|
23
|
+
await this.loadFromEnvFile()
|
|
24
|
+
} else if (storageMode === 'settings') {
|
|
25
|
+
this.loadFromSettings()
|
|
26
|
+
} else {
|
|
27
|
+
await this.loadFromSecrets()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
this.initialized = true
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private async loadFromSecrets(): Promise<void> {
|
|
34
|
+
const providers = ['openai', 'anthropic', 'google', 'azure', 'bedrock', 'huggingface']
|
|
35
|
+
|
|
36
|
+
for (const provider of providers) {
|
|
37
|
+
const apiKey = await this.getApiKey(provider)
|
|
38
|
+
if (apiKey) {
|
|
39
|
+
const keyName = this.getKeyNameForProvider(provider)
|
|
40
|
+
this.config[keyName] = apiKey
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private async loadFromEnvFile(): Promise<void> {
|
|
46
|
+
const envVars = await EnvFileService.detectEnvironmentVariables()
|
|
47
|
+
|
|
48
|
+
// Load all env vars into config
|
|
49
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
50
|
+
this.config[key] = value
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Also set AI_PROVIDER from env
|
|
54
|
+
if (!this.config.AI_PROVIDER && envVars.AI_PROVIDER) {
|
|
55
|
+
this.config.AI_PROVIDER = envVars.AI_PROVIDER
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private loadFromSettings(): void {
|
|
60
|
+
const wsConfig = vscode.workspace.getConfiguration('aiChangelog')
|
|
61
|
+
const providers = ['openai', 'anthropic', 'google', 'azure', 'bedrock', 'huggingface']
|
|
62
|
+
|
|
63
|
+
for (const provider of providers) {
|
|
64
|
+
const keyName = `${provider}ApiKey`
|
|
65
|
+
const apiKey = wsConfig.get<string>(keyName)
|
|
66
|
+
if (apiKey) {
|
|
67
|
+
const configKeyName = this.getKeyNameForProvider(provider)
|
|
68
|
+
this.config[configKeyName] = apiKey
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private getKeyNameForProvider(provider: string): string {
|
|
74
|
+
const keyMap: Record<string, string> = {
|
|
75
|
+
openai: 'OPENAI_API_KEY',
|
|
76
|
+
anthropic: 'ANTHROPIC_API_KEY',
|
|
77
|
+
google: 'GOOGLE_API_KEY',
|
|
78
|
+
azure: 'AZURE_OPENAI_KEY',
|
|
79
|
+
bedrock: 'AWS_ACCESS_KEY_ID',
|
|
80
|
+
huggingface: 'HUGGINGFACE_API_KEY',
|
|
81
|
+
}
|
|
82
|
+
return keyMap[provider] || `${provider.toUpperCase()}_API_KEY`
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
loadConfig() {
|
|
86
|
+
const wsConfig = vscode.workspace.getConfiguration('aiChangelog')
|
|
87
|
+
|
|
88
|
+
// Base configuration with defaults
|
|
89
|
+
this.config = {
|
|
90
|
+
AI_PROVIDER: wsConfig.get<string>('provider', 'openai'),
|
|
91
|
+
DEFAULT_ANALYSIS_MODE: wsConfig.get<string>('analysisMode', 'standard'),
|
|
92
|
+
OUTPUT_FORMAT: wsConfig.get<string>('outputFormat', 'markdown'),
|
|
93
|
+
INCLUDE_ATTRIBUTION: wsConfig.get<boolean>('includeAttribution', true),
|
|
94
|
+
RATE_LIMIT_DELAY: wsConfig.get<number>('rateLimitDelay', 1000),
|
|
95
|
+
MAX_RETRIES: wsConfig.get<number>('maxRetries', 3),
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Model override
|
|
99
|
+
const model = wsConfig.get<string>('model')
|
|
100
|
+
if (model) {
|
|
101
|
+
this.config.OPENAI_MODEL = model
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Ollama config
|
|
105
|
+
this.config.OLLAMA_HOST = wsConfig.get<string>('ollamaHost', 'http://localhost:11434')
|
|
106
|
+
this.config.OLLAMA_MODEL = wsConfig.get<string>('ollamaModel', 'llama3')
|
|
107
|
+
|
|
108
|
+
// Azure config
|
|
109
|
+
const azureEndpoint = wsConfig.get<string>('azureEndpoint')
|
|
110
|
+
const azureDeployment = wsConfig.get<string>('azureDeploymentName')
|
|
111
|
+
const azureVersion = wsConfig.get<string>('azureApiVersion')
|
|
112
|
+
|
|
113
|
+
if (azureEndpoint) this.config.AZURE_OPENAI_ENDPOINT = azureEndpoint
|
|
114
|
+
if (azureDeployment) this.config.AZURE_OPENAI_DEPLOYMENT_NAME = azureDeployment
|
|
115
|
+
if (azureVersion) this.config.AZURE_OPENAI_API_VERSION = azureVersion
|
|
116
|
+
|
|
117
|
+
// Vertex AI config
|
|
118
|
+
const vertexProject = wsConfig.get<string>('vertexProjectId')
|
|
119
|
+
const vertexLocation = wsConfig.get<string>('vertexLocation')
|
|
120
|
+
|
|
121
|
+
if (vertexProject) this.config.VERTEX_PROJECT_ID = vertexProject
|
|
122
|
+
if (vertexLocation) this.config.VERTEX_LOCATION = vertexLocation
|
|
123
|
+
|
|
124
|
+
// LM Studio config
|
|
125
|
+
const lmstudioUrl = wsConfig.get<string>('lmstudioBaseUrl')
|
|
126
|
+
if (lmstudioUrl) this.config.LMSTUDIO_BASE_URL = lmstudioUrl
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async getApiKey(provider: string): Promise<string | undefined> {
|
|
130
|
+
const keyMap: Record<string, string> = {
|
|
131
|
+
openai: 'OPENAI_API_KEY',
|
|
132
|
+
anthropic: 'ANTHROPIC_API_KEY',
|
|
133
|
+
google: 'GOOGLE_API_KEY',
|
|
134
|
+
azure: 'AZURE_OPENAI_KEY',
|
|
135
|
+
bedrock: 'AWS_ACCESS_KEY_ID',
|
|
136
|
+
huggingface: 'HUGGINGFACE_API_KEY',
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const secretKey = keyMap[provider]
|
|
140
|
+
if (secretKey) {
|
|
141
|
+
return await this.secretStorage.get(secretKey)
|
|
142
|
+
}
|
|
143
|
+
return undefined
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async storeApiKey(provider: string, key: string): Promise<void> {
|
|
147
|
+
const wsConfig = vscode.workspace.getConfiguration('aiChangelog')
|
|
148
|
+
const storageMode = wsConfig.get<string>('storageMode', 'secrets')
|
|
149
|
+
const secretKey = this.getKeyNameForProvider(provider)
|
|
150
|
+
|
|
151
|
+
if (storageMode === 'env') {
|
|
152
|
+
await EnvFileService.saveToEnvFile(secretKey, key)
|
|
153
|
+
} else if (storageMode === 'settings') {
|
|
154
|
+
const keyName = `${provider}ApiKey`
|
|
155
|
+
await wsConfig.update(keyName, key, vscode.ConfigurationTarget.Workspace)
|
|
156
|
+
} else {
|
|
157
|
+
await this.secretStorage.store(secretKey, key)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
this.config[secretKey] = key
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
get(key: string): any {
|
|
164
|
+
return this.config[key]
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
set(key: string, value: any) {
|
|
168
|
+
this.config[key] = value
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
getAll(): Record<string, any> {
|
|
172
|
+
return { ...this.config }
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
isSetupComplete(): boolean {
|
|
176
|
+
const wsConfig = vscode.workspace.getConfiguration('aiChangelog')
|
|
177
|
+
return wsConfig.get<boolean>('setupCompleted', false)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
isInitialized(): boolean {
|
|
181
|
+
return this.initialized
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
getCurrentProvider(): string {
|
|
185
|
+
const wsConfig = vscode.workspace.getConfiguration('aiChangelog')
|
|
186
|
+
return wsConfig.get<string>('provider') || this.config.AI_PROVIDER || 'openai'
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
getStorageMode(): string {
|
|
190
|
+
const wsConfig = vscode.workspace.getConfiguration('aiChangelog')
|
|
191
|
+
return wsConfig.get<string>('storageMode', 'secrets')
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async hasApiKey(provider: string): Promise<boolean> {
|
|
195
|
+
const keyName = this.getKeyNameForProvider(provider)
|
|
196
|
+
|
|
197
|
+
// Check in memory first
|
|
198
|
+
if (this.config[keyName]) {
|
|
199
|
+
return true
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Check based on storage mode
|
|
203
|
+
const storageMode = this.getStorageMode()
|
|
204
|
+
|
|
205
|
+
if (storageMode === 'secrets') {
|
|
206
|
+
const key = await this.secretStorage.get(keyName)
|
|
207
|
+
return !!key
|
|
208
|
+
} else if (storageMode === 'env') {
|
|
209
|
+
const envVars = await EnvFileService.detectEnvironmentVariables()
|
|
210
|
+
return !!envVars[keyName]
|
|
211
|
+
} else if (storageMode === 'settings') {
|
|
212
|
+
const wsConfig = vscode.workspace.getConfiguration('aiChangelog')
|
|
213
|
+
const settingsKey = `${provider}ApiKey`
|
|
214
|
+
const key = wsConfig.get<string>(settingsKey)
|
|
215
|
+
return !!key
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return false
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
get context() {
|
|
222
|
+
return { secrets: this.secretStorage }
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import * as vscode from 'vscode'
|
|
2
|
+
import { ExtensionConfigurationManager } from '../services/ExtensionConfigurationManager'
|
|
3
|
+
import { EnvFileService } from '../services/EnvFileService'
|
|
4
|
+
|
|
5
|
+
export interface SetupStatus {
|
|
6
|
+
isComplete: boolean
|
|
7
|
+
provider?: string
|
|
8
|
+
storageMode?: string
|
|
9
|
+
hasApiKey: boolean
|
|
10
|
+
errors: string[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class StatusService {
|
|
14
|
+
private static statusBarItem: vscode.StatusBarItem | null = null
|
|
15
|
+
private static outputChannel: vscode.OutputChannel | null = null
|
|
16
|
+
|
|
17
|
+
static initialize(context: vscode.ExtensionContext) {
|
|
18
|
+
this.statusBarItem = vscode.window.createStatusBarItem(
|
|
19
|
+
vscode.StatusBarAlignment.Right,
|
|
20
|
+
100
|
|
21
|
+
)
|
|
22
|
+
this.statusBarItem.command = 'ai-changelog.showStatus'
|
|
23
|
+
context.subscriptions.push(this.statusBarItem)
|
|
24
|
+
|
|
25
|
+
this.outputChannel = vscode.window.createOutputChannel('AI Changelog')
|
|
26
|
+
context.subscriptions.push(this.outputChannel)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static async updateStatus(configManager: ExtensionConfigurationManager) {
|
|
30
|
+
if (!this.statusBarItem) return
|
|
31
|
+
|
|
32
|
+
const status = await this.getStatus(configManager)
|
|
33
|
+
|
|
34
|
+
if (!status.isComplete) {
|
|
35
|
+
this.statusBarItem.text = '$(warning) AI Changelog: Setup Required'
|
|
36
|
+
this.statusBarItem.tooltip = 'Click to run setup wizard'
|
|
37
|
+
this.statusBarItem.backgroundColor = new vscode.ThemeColor(
|
|
38
|
+
'statusBarItem.warningBackground'
|
|
39
|
+
)
|
|
40
|
+
} else if (!status.hasApiKey) {
|
|
41
|
+
this.statusBarItem.text = `$(error) AI Changelog: No API Key`
|
|
42
|
+
this.statusBarItem.tooltip = `Provider: ${status.provider}\nNo API key configured`
|
|
43
|
+
this.statusBarItem.backgroundColor = new vscode.ThemeColor(
|
|
44
|
+
'statusBarItem.errorBackground'
|
|
45
|
+
)
|
|
46
|
+
} else if (status.errors.length > 0) {
|
|
47
|
+
this.statusBarItem.text = `$(alert) AI Changelog: ${status.provider}`
|
|
48
|
+
this.statusBarItem.tooltip = `Issues:\n${status.errors.join('\n')}`
|
|
49
|
+
this.statusBarItem.backgroundColor = new vscode.ThemeColor(
|
|
50
|
+
'statusBarItem.warningBackground'
|
|
51
|
+
)
|
|
52
|
+
} else {
|
|
53
|
+
this.statusBarItem.text = `$(check) AI Changelog: ${status.provider}`
|
|
54
|
+
this.statusBarItem.tooltip = `Ready to generate\nProvider: ${status.provider}\nStorage: ${status.storageMode}`
|
|
55
|
+
this.statusBarItem.backgroundColor = undefined
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
this.statusBarItem.show()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
static async getStatus(
|
|
62
|
+
configManager: ExtensionConfigurationManager
|
|
63
|
+
): Promise<SetupStatus> {
|
|
64
|
+
const status: SetupStatus = {
|
|
65
|
+
isComplete: configManager.isSetupComplete(),
|
|
66
|
+
provider: configManager.getCurrentProvider(),
|
|
67
|
+
storageMode: configManager.getStorageMode(),
|
|
68
|
+
hasApiKey: false,
|
|
69
|
+
errors: [],
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (status.isComplete) {
|
|
73
|
+
// Check if API key exists
|
|
74
|
+
const provider = status.provider || 'openai'
|
|
75
|
+
|
|
76
|
+
if (provider === 'ollama' || provider === 'lmstudio') {
|
|
77
|
+
status.hasApiKey = true // Local models don't need API keys
|
|
78
|
+
} else {
|
|
79
|
+
// Ensure config is initialized to load env vars
|
|
80
|
+
if (!configManager.isInitialized()) {
|
|
81
|
+
await configManager.initialize()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Check for API key in loaded config (includes env vars)
|
|
85
|
+
const keyName = configManager['getKeyNameForProvider'](provider)
|
|
86
|
+
const apiKey = configManager.get(keyName)
|
|
87
|
+
|
|
88
|
+
status.hasApiKey = !!apiKey && apiKey.length > 0
|
|
89
|
+
|
|
90
|
+
if (!status.hasApiKey) {
|
|
91
|
+
status.errors.push(`No API key found for ${provider}`)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Check environment file if using env storage
|
|
96
|
+
if (status.storageMode === 'env') {
|
|
97
|
+
const hasEnv = await EnvFileService.hasEnvVars()
|
|
98
|
+
if (!hasEnv) {
|
|
99
|
+
status.errors.push('Storage mode is "env" but no .env file found')
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return status
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
static log(message: string, show: boolean = false) {
|
|
108
|
+
if (this.outputChannel) {
|
|
109
|
+
const timestamp = new Date().toLocaleTimeString()
|
|
110
|
+
this.outputChannel.appendLine(`[${timestamp}] ${message}`)
|
|
111
|
+
if (show) {
|
|
112
|
+
this.outputChannel.show()
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
static async showStatusReport(configManager: ExtensionConfigurationManager) {
|
|
118
|
+
const status = await this.getStatus(configManager)
|
|
119
|
+
|
|
120
|
+
const lines: string[] = [
|
|
121
|
+
'# AI Changelog Generator - Configuration Status',
|
|
122
|
+
'',
|
|
123
|
+
'## Setup Status',
|
|
124
|
+
`- Setup Complete: ${status.isComplete ? '✅ Yes' : '❌ No'}`,
|
|
125
|
+
`- AI Provider: ${status.provider || 'Not configured'}`,
|
|
126
|
+
`- Storage Mode: ${status.storageMode || 'Not configured'}`,
|
|
127
|
+
`- Has API Key: ${status.hasApiKey ? '✅ Yes' : '❌ No'}`,
|
|
128
|
+
'',
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
if (status.errors.length > 0) {
|
|
132
|
+
lines.push('## ⚠️ Issues Found')
|
|
133
|
+
status.errors.forEach((error) => lines.push(`- ${error}`))
|
|
134
|
+
lines.push('')
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Get detailed config
|
|
138
|
+
const wsConfig = vscode.workspace.getConfiguration('aiChangelog')
|
|
139
|
+
lines.push('## Current Settings')
|
|
140
|
+
lines.push(`- Provider: ${wsConfig.get('provider')}`)
|
|
141
|
+
lines.push(`- Model: ${wsConfig.get('model')}`)
|
|
142
|
+
lines.push(`- Temperature: ${wsConfig.get('temperature')}`)
|
|
143
|
+
lines.push(`- Storage Mode: ${wsConfig.get('storageMode')}`)
|
|
144
|
+
lines.push(`- Env File Path: ${wsConfig.get('envFilePath')}`)
|
|
145
|
+
lines.push(`- Auto Detect Env: ${wsConfig.get('autoDetectEnv')}`)
|
|
146
|
+
lines.push(`- Analysis Mode: ${wsConfig.get('analysisMode')}`)
|
|
147
|
+
lines.push('')
|
|
148
|
+
|
|
149
|
+
// Provider-specific settings
|
|
150
|
+
if (status.provider === 'ollama') {
|
|
151
|
+
lines.push('## Ollama Settings')
|
|
152
|
+
lines.push(`- Host: ${wsConfig.get('ollamaHost')}`)
|
|
153
|
+
lines.push(`- Model: ${wsConfig.get('ollamaModel')}`)
|
|
154
|
+
lines.push('')
|
|
155
|
+
} else if (status.provider === 'azure') {
|
|
156
|
+
lines.push('## Azure Settings')
|
|
157
|
+
lines.push(`- Endpoint: ${wsConfig.get('azureEndpoint') || 'Not set'}`)
|
|
158
|
+
lines.push(
|
|
159
|
+
`- Deployment: ${wsConfig.get('azureDeploymentName') || 'Not set'}`
|
|
160
|
+
)
|
|
161
|
+
lines.push(`- API Version: ${wsConfig.get('azureApiVersion')}`)
|
|
162
|
+
lines.push('')
|
|
163
|
+
} else if (status.provider === 'lmstudio') {
|
|
164
|
+
lines.push('## LM Studio Settings')
|
|
165
|
+
lines.push(`- Base URL: ${wsConfig.get('lmstudioBaseUrl')}`)
|
|
166
|
+
lines.push('')
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Environment file detection
|
|
170
|
+
if (wsConfig.get('autoDetectEnv')) {
|
|
171
|
+
const envVars = await EnvFileService.detectEnvironmentVariables()
|
|
172
|
+
if (Object.keys(envVars).length > 0) {
|
|
173
|
+
lines.push('## 🔍 Detected Environment Variables')
|
|
174
|
+
Object.keys(envVars).forEach((key) => {
|
|
175
|
+
lines.push(`- ${key}: ✅ Found`)
|
|
176
|
+
})
|
|
177
|
+
lines.push('')
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Recommendations
|
|
182
|
+
lines.push('## 💡 Recommendations')
|
|
183
|
+
if (!status.isComplete) {
|
|
184
|
+
lines.push('- Run the setup wizard: `AI Changelog: Setup Wizard`')
|
|
185
|
+
} else if (!status.hasApiKey) {
|
|
186
|
+
lines.push(
|
|
187
|
+
`- Configure API key for ${status.provider}: \`AI Changelog: Configure AI Provider\``
|
|
188
|
+
)
|
|
189
|
+
} else if (status.storageMode === 'settings') {
|
|
190
|
+
lines.push(
|
|
191
|
+
'- Consider using secure storage instead of settings.json for better security'
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Open in new document
|
|
196
|
+
const doc = await vscode.workspace.openTextDocument({
|
|
197
|
+
content: lines.join('\n'),
|
|
198
|
+
language: 'markdown',
|
|
199
|
+
})
|
|
200
|
+
await vscode.window.showTextDocument(doc)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
static dispose() {
|
|
204
|
+
this.statusBarItem?.dispose()
|
|
205
|
+
this.outputChannel?.dispose()
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
static showOutputChannel() {
|
|
209
|
+
this.outputChannel?.show()
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for core ai-changelog-generator modules
|
|
3
|
+
* These allow TypeScript to understand imports from the workspace dependency
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
declare module '@entro314labs/ai-changelog-generator/src/domains/git/git-manager.js' {
|
|
7
|
+
export class GitManager {
|
|
8
|
+
constructor(options: { cwd: string })
|
|
9
|
+
execGitSafe(command: string): string
|
|
10
|
+
execGit(command: string): string
|
|
11
|
+
getRepoRoot(): string
|
|
12
|
+
getCurrentBranch(): string
|
|
13
|
+
getTags(): string[]
|
|
14
|
+
getCommits(count?: number): any[]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
declare module '@entro314labs/ai-changelog-generator/src/domains/git/git.service.js' {
|
|
19
|
+
export class GitService {
|
|
20
|
+
constructor(gitManager: any, tagger: any)
|
|
21
|
+
analyzeWorkingDirectoryFileChange(status: string, filePath: string): Promise<any>
|
|
22
|
+
getWorkingDirectoryChanges(): Promise<any[]>
|
|
23
|
+
getCommitsBetweenTags(fromTag: string, toTag?: string): Promise<any[]>
|
|
24
|
+
analyzeCommit(commitHash: string): Promise<any>
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
declare module '@entro314labs/ai-changelog-generator/src/domains/git/commit-tagger.js' {
|
|
29
|
+
export class CommitTagger {
|
|
30
|
+
constructor()
|
|
31
|
+
categorizeCommit(analysis: any): any
|
|
32
|
+
determineImportance(analysis: any): string
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
declare module '@entro314labs/ai-changelog-generator/src/application/services/application.service.js' {
|
|
37
|
+
export class ApplicationService {
|
|
38
|
+
constructor(options: { configManager: any; cwd: string })
|
|
39
|
+
generateCommitMessage(): Promise<{
|
|
40
|
+
success: boolean
|
|
41
|
+
suggestions?: string[]
|
|
42
|
+
error?: string
|
|
43
|
+
}>
|
|
44
|
+
generateChangelogFromChanges(version: string): Promise<{
|
|
45
|
+
success: boolean
|
|
46
|
+
changelog?: string
|
|
47
|
+
error?: string
|
|
48
|
+
}>
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
declare module '@entro314labs/ai-changelog-generator/src/infrastructure/config/configuration.manager.js' {
|
|
53
|
+
export class ConfigurationManager {
|
|
54
|
+
constructor()
|
|
55
|
+
config: Record<string, any>
|
|
56
|
+
loadConfig(): Record<string, any>
|
|
57
|
+
getAll(): Record<string, any>
|
|
58
|
+
get(key: string): any
|
|
59
|
+
set(key: string, value: any): void
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
declare module '@entro314labs/ai-changelog-generator/src/application/orchestrators/changelog.orchestrator.js' {
|
|
64
|
+
export class ChangelogOrchestrator {
|
|
65
|
+
constructor(options: any)
|
|
66
|
+
generateChangelog(options?: any): Promise<any>
|
|
67
|
+
analyzeChanges(): Promise<any>
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
declare module '@entro314labs/ai-changelog-generator/src/domains/ai/ai-analysis.service.js' {
|
|
72
|
+
export class AIAnalysisService {
|
|
73
|
+
constructor(aiProvider: any, promptEngine: any, tagger: any, analysisMode: string)
|
|
74
|
+
analyzeCommit(commit: any): Promise<any>
|
|
75
|
+
analyzeFileChange(analysis: any): Promise<any>
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
declare module '@entro314labs/ai-changelog-generator/src/infrastructure/providers/provider-manager.service.js' {
|
|
80
|
+
export class ProviderManagerService {
|
|
81
|
+
constructor(config: any)
|
|
82
|
+
getProvider(): any
|
|
83
|
+
isProviderAvailable(providerName: string): boolean
|
|
84
|
+
}
|
|
85
|
+
}
|