promptarchitect 0.6.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/.vscodeignore +7 -0
- package/CHANGELOG.md +28 -0
- package/LICENSE +44 -0
- package/README.md +200 -0
- package/docs/CHAT_UI_REDESIGN_PLAN.md +371 -0
- package/images/hub-icon.svg +6 -0
- package/images/prompt-lab-icon.svg +11 -0
- package/package.json +519 -0
- package/src/agentPrompts.ts +278 -0
- package/src/agentService.ts +630 -0
- package/src/api.ts +223 -0
- package/src/authService.ts +556 -0
- package/src/chatPanel.ts +979 -0
- package/src/extension.ts +822 -0
- package/src/providers/aiChatViewProvider.ts +1023 -0
- package/src/providers/environmentTreeProvider.ts +311 -0
- package/src/providers/index.ts +9 -0
- package/src/providers/notesTreeProvider.ts +301 -0
- package/src/providers/quickAccessTreeProvider.ts +328 -0
- package/src/providers/scriptsTreeProvider.ts +324 -0
- package/src/refinerPanel.ts +620 -0
- package/src/templates.ts +61 -0
- package/src/workspaceIndexer.ts +766 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PromptArchitect Authentication Service
|
|
3
|
+
*
|
|
4
|
+
* Manages user authentication for the VS Code extension.
|
|
5
|
+
* Uses VS Code's built-in authentication API with a custom provider
|
|
6
|
+
* that connects to our backend.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as vscode from 'vscode';
|
|
10
|
+
|
|
11
|
+
const AUTH_PROVIDER_ID = 'promptarchitect';
|
|
12
|
+
const AUTH_PROVIDER_LABEL = 'PromptArchitect';
|
|
13
|
+
const SCOPES = ['user:read', 'prompts:write'];
|
|
14
|
+
|
|
15
|
+
export interface UserSession {
|
|
16
|
+
id: string;
|
|
17
|
+
accessToken: string;
|
|
18
|
+
account: {
|
|
19
|
+
id: string;
|
|
20
|
+
email: string;
|
|
21
|
+
displayName: string;
|
|
22
|
+
};
|
|
23
|
+
expiresAt?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface AuthState {
|
|
27
|
+
isAuthenticated: boolean;
|
|
28
|
+
session: UserSession | null;
|
|
29
|
+
error: string | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// API Response types
|
|
33
|
+
interface CheckEmailResponse {
|
|
34
|
+
exists: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface AuthResponse {
|
|
38
|
+
sessionId: string;
|
|
39
|
+
accessToken: string;
|
|
40
|
+
expiresIn?: number;
|
|
41
|
+
user: {
|
|
42
|
+
id: string;
|
|
43
|
+
email: string;
|
|
44
|
+
displayName?: string;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface AuthErrorResponse {
|
|
49
|
+
message?: string;
|
|
50
|
+
error?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class AuthService implements vscode.Disposable {
|
|
54
|
+
private static instance: AuthService;
|
|
55
|
+
private _session: vscode.AuthenticationSession | null = null;
|
|
56
|
+
private _onDidChangeAuth = new vscode.EventEmitter<boolean>();
|
|
57
|
+
public readonly onDidChangeAuth = this._onDidChangeAuth.event;
|
|
58
|
+
|
|
59
|
+
private context: vscode.ExtensionContext;
|
|
60
|
+
private apiEndpoint: string;
|
|
61
|
+
private disposables: vscode.Disposable[] = [];
|
|
62
|
+
|
|
63
|
+
private constructor(context: vscode.ExtensionContext, apiEndpoint: string) {
|
|
64
|
+
this.context = context;
|
|
65
|
+
this.apiEndpoint = apiEndpoint;
|
|
66
|
+
|
|
67
|
+
// Listen for session changes
|
|
68
|
+
this.disposables.push(
|
|
69
|
+
vscode.authentication.onDidChangeSessions((e) => {
|
|
70
|
+
if (e.provider.id === AUTH_PROVIDER_ID) {
|
|
71
|
+
this.checkAuthStatus();
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
static getInstance(context: vscode.ExtensionContext, apiEndpoint: string): AuthService {
|
|
78
|
+
if (!AuthService.instance) {
|
|
79
|
+
AuthService.instance = new AuthService(context, apiEndpoint);
|
|
80
|
+
}
|
|
81
|
+
return AuthService.instance;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Check if user is currently authenticated
|
|
86
|
+
*/
|
|
87
|
+
async isAuthenticated(): Promise<boolean> {
|
|
88
|
+
// First check stored session
|
|
89
|
+
const storedSession = this.context.globalState.get<UserSession>('authSession');
|
|
90
|
+
if (storedSession && storedSession.expiresAt && storedSession.expiresAt > Date.now()) {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Check with API
|
|
95
|
+
try {
|
|
96
|
+
const session = await this.getSession(false);
|
|
97
|
+
return session !== null;
|
|
98
|
+
} catch {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Get current session or prompt for login
|
|
105
|
+
*/
|
|
106
|
+
async getSession(createIfNone: boolean = true): Promise<UserSession | null> {
|
|
107
|
+
// Check stored session first
|
|
108
|
+
const storedSession = this.context.globalState.get<UserSession>('authSession');
|
|
109
|
+
if (storedSession && storedSession.expiresAt && storedSession.expiresAt > Date.now()) {
|
|
110
|
+
return storedSession;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (!createIfNone) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Prompt for login
|
|
118
|
+
return this.signIn();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get access token for API requests
|
|
123
|
+
*/
|
|
124
|
+
async getAccessToken(): Promise<string | null> {
|
|
125
|
+
const session = await this.getSession(false);
|
|
126
|
+
return session?.accessToken || null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Sign in user - opens browser for OAuth flow
|
|
131
|
+
*/
|
|
132
|
+
async signIn(): Promise<UserSession | null> {
|
|
133
|
+
const choice = await vscode.window.showInformationMessage(
|
|
134
|
+
'🔐 Welcome to PromptArchitect! Please sign in or create an account.',
|
|
135
|
+
{ modal: true },
|
|
136
|
+
'Sign In',
|
|
137
|
+
'Create Account'
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
if (!choice) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (choice === 'Create Account') {
|
|
145
|
+
vscode.env.openExternal(vscode.Uri.parse('https://promptarchitectlabs.com/'));
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Sign In Flow
|
|
150
|
+
const providerChoice = await vscode.window.showQuickPick(
|
|
151
|
+
[
|
|
152
|
+
{ label: '$(globe) Sign in with Google', value: 'google' },
|
|
153
|
+
{ label: '$(github) Sign in with GitHub', value: 'github' },
|
|
154
|
+
{ label: '$(mail) Sign in with Email', value: 'email' },
|
|
155
|
+
],
|
|
156
|
+
{
|
|
157
|
+
placeHolder: 'Select a sign-in method',
|
|
158
|
+
title: 'Sign In to PromptArchitect',
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
if (!providerChoice) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
const provider = providerChoice.value as 'google' | 'github' | 'email';
|
|
168
|
+
|
|
169
|
+
// For email, collect credentials
|
|
170
|
+
if (provider === 'email') {
|
|
171
|
+
return this.signInWithEmail();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// For OAuth providers, open browser
|
|
175
|
+
return this.signInWithOAuth(provider);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
vscode.window.showErrorMessage(`Sign in failed: ${error}`);
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Sign in with email/password
|
|
184
|
+
*/
|
|
185
|
+
private async signInWithEmail(): Promise<UserSession | null> {
|
|
186
|
+
const email = await vscode.window.showInputBox({
|
|
187
|
+
prompt: 'Enter your email address',
|
|
188
|
+
placeHolder: 'you@example.com',
|
|
189
|
+
ignoreFocusOut: true,
|
|
190
|
+
validateInput: (value) => {
|
|
191
|
+
if (!value || !value.includes('@')) {
|
|
192
|
+
return 'Please enter a valid email address';
|
|
193
|
+
}
|
|
194
|
+
return null;
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
if (!email) return null;
|
|
199
|
+
|
|
200
|
+
const password = await vscode.window.showInputBox({
|
|
201
|
+
prompt: 'Enter your password',
|
|
202
|
+
password: true,
|
|
203
|
+
ignoreFocusOut: true,
|
|
204
|
+
validateInput: (value) => {
|
|
205
|
+
if (!value || value.length < 6) {
|
|
206
|
+
return 'Password must be at least 6 characters';
|
|
207
|
+
}
|
|
208
|
+
return null;
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
if (!password) return null;
|
|
213
|
+
|
|
214
|
+
// Check if this is a new user
|
|
215
|
+
const isNewUser = await this.checkIfNewUser(email);
|
|
216
|
+
|
|
217
|
+
if (isNewUser) {
|
|
218
|
+
const confirm = await vscode.window.showInformationMessage(
|
|
219
|
+
`No account found for ${email}. Create a new account?`,
|
|
220
|
+
{ modal: true },
|
|
221
|
+
'Create Account',
|
|
222
|
+
'Cancel'
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
if (confirm !== 'Create Account') {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return this.createAccount(email, password);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return this.authenticateWithEmail(email, password);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Check if email is registered
|
|
237
|
+
*/
|
|
238
|
+
private async checkIfNewUser(email: string): Promise<boolean> {
|
|
239
|
+
try {
|
|
240
|
+
const response = await fetch(`${this.apiEndpoint}/auth/check-email`, {
|
|
241
|
+
method: 'POST',
|
|
242
|
+
headers: { 'Content-Type': 'application/json' },
|
|
243
|
+
body: JSON.stringify({ email }),
|
|
244
|
+
});
|
|
245
|
+
const data = await response.json() as CheckEmailResponse;
|
|
246
|
+
return !data.exists;
|
|
247
|
+
} catch {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Authenticate with email/password
|
|
254
|
+
*/
|
|
255
|
+
private async authenticateWithEmail(email: string, password: string): Promise<UserSession | null> {
|
|
256
|
+
try {
|
|
257
|
+
const response = await fetch(`${this.apiEndpoint}/auth/login`, {
|
|
258
|
+
method: 'POST',
|
|
259
|
+
headers: { 'Content-Type': 'application/json' },
|
|
260
|
+
body: JSON.stringify({ email, password, client: 'vscode' }),
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
if (!response.ok) {
|
|
264
|
+
const error = await response.json() as AuthErrorResponse;
|
|
265
|
+
throw new Error(error.message || 'Authentication failed');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const data = await response.json() as AuthResponse;
|
|
269
|
+
const session: UserSession = {
|
|
270
|
+
id: data.sessionId,
|
|
271
|
+
accessToken: data.accessToken,
|
|
272
|
+
account: {
|
|
273
|
+
id: data.user.id,
|
|
274
|
+
email: data.user.email,
|
|
275
|
+
displayName: data.user.displayName || email.split('@')[0],
|
|
276
|
+
},
|
|
277
|
+
expiresAt: Date.now() + (data.expiresIn || 86400) * 1000,
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// Store session
|
|
281
|
+
await this.context.globalState.update('authSession', session);
|
|
282
|
+
this._onDidChangeAuth.fire(true);
|
|
283
|
+
|
|
284
|
+
vscode.window.showInformationMessage(`✅ Welcome back, ${session.account.displayName}!`);
|
|
285
|
+
return session;
|
|
286
|
+
} catch (error) {
|
|
287
|
+
vscode.window.showErrorMessage(`Login failed: ${error}`);
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Create a new account
|
|
294
|
+
*/
|
|
295
|
+
private async createAccount(email: string, password: string): Promise<UserSession | null> {
|
|
296
|
+
// Get display name
|
|
297
|
+
const displayName = await vscode.window.showInputBox({
|
|
298
|
+
prompt: 'Choose a display name',
|
|
299
|
+
placeHolder: 'Your Name',
|
|
300
|
+
ignoreFocusOut: true,
|
|
301
|
+
value: email.split('@')[0],
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
if (!displayName) return null;
|
|
305
|
+
|
|
306
|
+
try {
|
|
307
|
+
const response = await fetch(`${this.apiEndpoint}/auth/register`, {
|
|
308
|
+
method: 'POST',
|
|
309
|
+
headers: { 'Content-Type': 'application/json' },
|
|
310
|
+
body: JSON.stringify({ email, password, displayName, client: 'vscode' }),
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
if (!response.ok) {
|
|
314
|
+
const error = await response.json() as AuthErrorResponse;
|
|
315
|
+
throw new Error(error.message || 'Registration failed');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const data = await response.json() as AuthResponse;
|
|
319
|
+
const session: UserSession = {
|
|
320
|
+
id: data.sessionId,
|
|
321
|
+
accessToken: data.accessToken,
|
|
322
|
+
account: {
|
|
323
|
+
id: data.user.id,
|
|
324
|
+
email: data.user.email,
|
|
325
|
+
displayName: data.user.displayName || displayName,
|
|
326
|
+
},
|
|
327
|
+
expiresAt: Date.now() + (data.expiresIn || 86400) * 1000,
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
// Store session
|
|
331
|
+
await this.context.globalState.update('authSession', session);
|
|
332
|
+
this._onDidChangeAuth.fire(true);
|
|
333
|
+
|
|
334
|
+
vscode.window.showInformationMessage(`🎉 Account created! Welcome, ${session.account.displayName}!`);
|
|
335
|
+
return session;
|
|
336
|
+
} catch (error) {
|
|
337
|
+
vscode.window.showErrorMessage(`Registration failed: ${error}`);
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Sign in with OAuth (Google/GitHub)
|
|
344
|
+
*/
|
|
345
|
+
private async signInWithOAuth(provider: 'google' | 'github'): Promise<UserSession | null> {
|
|
346
|
+
// Generate state for CSRF protection
|
|
347
|
+
const state = this.generateRandomString(32);
|
|
348
|
+
await this.context.globalState.update('authState', state);
|
|
349
|
+
|
|
350
|
+
// Build OAuth URL
|
|
351
|
+
// Use the official callback URL scheme
|
|
352
|
+
const callbackUri = await vscode.env.asExternalUri(
|
|
353
|
+
vscode.Uri.parse(`${vscode.env.uriScheme}://merabylabs.promptarchitect/callback`)
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
// Use frontend URL for auth
|
|
357
|
+
const authUrl = new URL(`https://promptarchitectlabs.com/vscode-auth`);
|
|
358
|
+
authUrl.searchParams.set('state', state);
|
|
359
|
+
authUrl.searchParams.set('redirect_uri', callbackUri.toString(true)); // Use true to skip encoding
|
|
360
|
+
authUrl.searchParams.set('client', 'vscode');
|
|
361
|
+
authUrl.searchParams.set('provider', provider);
|
|
362
|
+
|
|
363
|
+
// Open browser for OAuth
|
|
364
|
+
await vscode.env.openExternal(vscode.Uri.parse(authUrl.toString()));
|
|
365
|
+
|
|
366
|
+
// Wait for callback (with timeout)
|
|
367
|
+
return new Promise((resolve) => {
|
|
368
|
+
const timeout = setTimeout(() => {
|
|
369
|
+
disposable.dispose();
|
|
370
|
+
resolve(null);
|
|
371
|
+
}, 120000); // 2 minute timeout
|
|
372
|
+
|
|
373
|
+
const disposable = vscode.window.registerUriHandler({
|
|
374
|
+
handleUri: async (uri) => {
|
|
375
|
+
clearTimeout(timeout);
|
|
376
|
+
disposable.dispose();
|
|
377
|
+
|
|
378
|
+
if (uri.path === '/callback') {
|
|
379
|
+
const query = new URLSearchParams(uri.query);
|
|
380
|
+
const returnedState = query.get('state');
|
|
381
|
+
const code = query.get('code');
|
|
382
|
+
const token = query.get('token');
|
|
383
|
+
const uid = query.get('uid');
|
|
384
|
+
const email = query.get('email');
|
|
385
|
+
const displayName = query.get('displayName');
|
|
386
|
+
const error = query.get('error');
|
|
387
|
+
|
|
388
|
+
if (error) {
|
|
389
|
+
vscode.window.showErrorMessage(`Authentication failed: ${error}`);
|
|
390
|
+
resolve(null);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Verify state
|
|
395
|
+
const savedState = this.context.globalState.get<string>('authState');
|
|
396
|
+
if (returnedState !== savedState) {
|
|
397
|
+
vscode.window.showErrorMessage('Authentication failed: Invalid state');
|
|
398
|
+
resolve(null);
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (token) {
|
|
403
|
+
// Implicit flow from frontend
|
|
404
|
+
const session: UserSession = {
|
|
405
|
+
id: uid || 'user',
|
|
406
|
+
accessToken: token,
|
|
407
|
+
account: {
|
|
408
|
+
id: uid || 'user',
|
|
409
|
+
email: email || 'user@example.com',
|
|
410
|
+
displayName: displayName || 'User',
|
|
411
|
+
},
|
|
412
|
+
expiresAt: Date.now() + 3600 * 1000,
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
await this.context.globalState.update('authSession', session);
|
|
416
|
+
this._onDidChangeAuth.fire(true);
|
|
417
|
+
vscode.window.showInformationMessage(`✅ Welcome, ${session.account.displayName}!`);
|
|
418
|
+
resolve(session);
|
|
419
|
+
} else if (code) {
|
|
420
|
+
const session = await this.exchangeCodeForSession(code, provider);
|
|
421
|
+
resolve(session);
|
|
422
|
+
} else {
|
|
423
|
+
resolve(null);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
},
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Exchange OAuth code for session
|
|
433
|
+
*/
|
|
434
|
+
private async exchangeCodeForSession(code: string, provider: string): Promise<UserSession | null> {
|
|
435
|
+
try {
|
|
436
|
+
const response = await fetch(`${this.apiEndpoint}/auth/callback/${provider}`, {
|
|
437
|
+
method: 'POST',
|
|
438
|
+
headers: { 'Content-Type': 'application/json' },
|
|
439
|
+
body: JSON.stringify({ code, client: 'vscode' }),
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
if (!response.ok) {
|
|
443
|
+
throw new Error('Failed to exchange code');
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const data = await response.json() as AuthResponse;
|
|
447
|
+
const session: UserSession = {
|
|
448
|
+
id: data.sessionId,
|
|
449
|
+
accessToken: data.accessToken,
|
|
450
|
+
account: {
|
|
451
|
+
id: data.user.id,
|
|
452
|
+
email: data.user.email,
|
|
453
|
+
displayName: data.user.displayName || 'User',
|
|
454
|
+
},
|
|
455
|
+
expiresAt: Date.now() + (data.expiresIn || 86400) * 1000,
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
await this.context.globalState.update('authSession', session);
|
|
459
|
+
this._onDidChangeAuth.fire(true);
|
|
460
|
+
|
|
461
|
+
vscode.window.showInformationMessage(`✅ Welcome, ${session.account.displayName}!`);
|
|
462
|
+
return session;
|
|
463
|
+
} catch (error) {
|
|
464
|
+
vscode.window.showErrorMessage(`Authentication failed: ${error}`);
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Sign out the current user
|
|
471
|
+
*/
|
|
472
|
+
async signOut(): Promise<void> {
|
|
473
|
+
const session = await this.getSession(false);
|
|
474
|
+
|
|
475
|
+
if (session) {
|
|
476
|
+
try {
|
|
477
|
+
// Revoke session on server
|
|
478
|
+
await fetch(`${this.apiEndpoint}/auth/logout`, {
|
|
479
|
+
method: 'POST',
|
|
480
|
+
headers: {
|
|
481
|
+
'Content-Type': 'application/json',
|
|
482
|
+
'Authorization': `Bearer ${session.accessToken}`,
|
|
483
|
+
},
|
|
484
|
+
});
|
|
485
|
+
} catch {
|
|
486
|
+
// Ignore errors during logout
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Clear stored session
|
|
491
|
+
await this.context.globalState.update('authSession', undefined);
|
|
492
|
+
await this.context.globalState.update('authState', undefined);
|
|
493
|
+
this._session = null;
|
|
494
|
+
this._onDidChangeAuth.fire(false);
|
|
495
|
+
|
|
496
|
+
vscode.window.showInformationMessage('👋 Signed out successfully');
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Get current user info
|
|
501
|
+
*/
|
|
502
|
+
async getCurrentUser(): Promise<UserSession['account'] | null> {
|
|
503
|
+
const session = await this.getSession(false);
|
|
504
|
+
return session?.account || null;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Check auth status and update internal state
|
|
509
|
+
*/
|
|
510
|
+
private async checkAuthStatus(): Promise<void> {
|
|
511
|
+
const isAuth = await this.isAuthenticated();
|
|
512
|
+
this._onDidChangeAuth.fire(isAuth);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Generate random string for CSRF state
|
|
517
|
+
*/
|
|
518
|
+
private generateRandomString(length: number): string {
|
|
519
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
520
|
+
let result = '';
|
|
521
|
+
for (let i = 0; i < length; i++) {
|
|
522
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
523
|
+
}
|
|
524
|
+
return result;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Show auth required message and prompt to sign in
|
|
529
|
+
*/
|
|
530
|
+
async requireAuth(): Promise<boolean> {
|
|
531
|
+
const isAuth = await this.isAuthenticated();
|
|
532
|
+
|
|
533
|
+
if (isAuth) {
|
|
534
|
+
return true;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const result = await vscode.window.showWarningMessage(
|
|
538
|
+
'🔐 Authentication Required\n\nSign in to continue using PromptArchitect. Your account enables prompt history, preferences, and access to all features.',
|
|
539
|
+
{ modal: true },
|
|
540
|
+
'Sign In',
|
|
541
|
+
'Create Account'
|
|
542
|
+
);
|
|
543
|
+
|
|
544
|
+
if (result === 'Sign In' || result === 'Create Account') {
|
|
545
|
+
const session = await this.signIn();
|
|
546
|
+
return session !== null;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
return false;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
dispose(): void {
|
|
553
|
+
this._onDidChangeAuth.dispose();
|
|
554
|
+
this.disposables.forEach((d) => d.dispose());
|
|
555
|
+
}
|
|
556
|
+
}
|