vibehub-cli 1.0.44 → 1.1.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.
@@ -1,58 +1,134 @@
1
- import { GlobalConfigManager } from './global-config.js';
2
- import { authenticateWithFirebase } from './firebase-auth.js';
3
1
  import chalk from 'chalk';
2
+ import { ProfileManager } from './profile-manager.js';
3
+ import { SupabaseAuth } from './supabase-auth.js';
4
4
  /**
5
- * Get authentication token - checks global config first, prompts if needed
6
- * This is the main entry point for authentication in all commands
5
+ * Get authentication token - uses profile system with automatic token refresh.
6
+ * This is the main entry point for authentication in all commands.
7
+ *
8
+ * Flow:
9
+ * 1. Check for migration from old format (prompts browser login if needed)
10
+ * 2. Get active profile (project-level override or default)
11
+ * 3. If no profile exists, prompt browser login
12
+ * 4. If token expired/expiring, silently refresh using refresh token
13
+ * 5. Return valid token
7
14
  */
8
15
  export async function getAuthToken(forceReauth = false) {
9
- const configManager = new GlobalConfigManager();
10
- // If force reauth, clear existing auth
16
+ const profileManager = new ProfileManager();
17
+ // Check for migration from old format
18
+ const needsMigration = await profileManager.migrateIfNeeded();
19
+ if (needsMigration) {
20
+ console.log('');
21
+ console.log(chalk.yellow('═'.repeat(55)));
22
+ console.log(chalk.yellow.bold(' VibeHub Authentication Upgrade'));
23
+ console.log(chalk.yellow('═'.repeat(55)));
24
+ console.log('');
25
+ console.log('VibeHub now uses Google Sign-In for better security');
26
+ console.log('and seamless authentication across all your devices.');
27
+ console.log('');
28
+ console.log(chalk.gray('Your existing configuration has been backed up.'));
29
+ console.log('');
30
+ }
31
+ // If force reauth, delete current profile and re-authenticate
11
32
  if (forceReauth) {
12
- await configManager.clearAuth();
13
- }
14
- // Check for stored token
15
- let token = await configManager.getToken();
16
- let email = await configManager.getUserEmail();
17
- // If we have a valid token, return it
18
- if (token && email) {
19
- return { email, token };
20
- }
21
- // No valid token - need to authenticate
22
- console.log(chalk.yellow('🔐 Authentication required'));
23
- console.log(chalk.gray('Please sign in to continue...\n'));
24
- // Authenticate with Firebase
25
- const authResult = await authenticateWithFirebase(email || undefined);
26
- // Save token globally for future use
27
- await configManager.saveToken(authResult.email, authResult.token, 3600 // 1 hour expiry (Firebase tokens typically last 1 hour)
28
- );
29
- console.log(chalk.green(`✅ Authenticated as ${authResult.email}`));
30
- console.log(chalk.gray('Token saved globally - you won\'t need to authenticate again for other projects.\n'));
33
+ const activeProfile = await profileManager.getActiveProfile();
34
+ if (activeProfile) {
35
+ await profileManager.deleteProfile(activeProfile.name);
36
+ }
37
+ }
38
+ // Get active profile (project-level override or default)
39
+ let profile = await profileManager.getActiveProfile();
40
+ // If no profile exists, prompt for authentication
41
+ if (!profile) {
42
+ console.log('');
43
+ console.log(chalk.yellow('Authentication required'));
44
+ console.log(chalk.blue('Opening browser for Google sign-in...'));
45
+ console.log('');
46
+ console.log(chalk.gray('If your browser doesn\'t open automatically, please visit:'));
47
+ console.log(chalk.gray('https://vibehub.co.in/cli-auth'));
48
+ console.log('');
49
+ try {
50
+ const supabaseAuth = new SupabaseAuth();
51
+ const profileData = await supabaseAuth.loginWithGoogle();
52
+ // Save to default profile
53
+ await profileManager.saveProfile('default', profileData);
54
+ profile = await profileManager.getProfile('default');
55
+ console.log('');
56
+ console.log(chalk.green(`✓ Authenticated as ${profileData.email}`));
57
+ console.log(chalk.gray('Token saved - you won\'t need to authenticate again for a while.'));
58
+ console.log('');
59
+ }
60
+ catch (error) {
61
+ console.log('');
62
+ console.log(chalk.red('✗ Authentication failed'));
63
+ console.log(chalk.red(` ${error.message}`));
64
+ console.log('');
65
+ throw error;
66
+ }
67
+ }
68
+ if (!profile) {
69
+ throw new Error('Authentication failed. Please run \'vibe login\' to sign in.');
70
+ }
71
+ // Refresh token if needed (silent refresh)
72
+ try {
73
+ profile = await profileManager.refreshTokenIfNeeded(profile);
74
+ }
75
+ catch (error) {
76
+ // Token refresh failed - need to re-authenticate
77
+ console.log('');
78
+ console.log(chalk.yellow('Session expired. Re-authenticating...'));
79
+ console.log(chalk.blue('Opening browser for Google sign-in...'));
80
+ console.log('');
81
+ const supabaseAuth = new SupabaseAuth();
82
+ const profileData = await supabaseAuth.loginWithGoogle();
83
+ await profileManager.saveProfile(profile.name, profileData);
84
+ profile = await profileManager.getProfile(profile.name);
85
+ if (!profile) {
86
+ throw new Error('Authentication failed after refresh.');
87
+ }
88
+ console.log(chalk.green(`✓ Re-authenticated as ${profile.email}`));
89
+ console.log('');
90
+ }
31
91
  return {
32
- email: authResult.email,
33
- token: authResult.token
92
+ email: profile.email,
93
+ token: profile.accessToken
34
94
  };
35
95
  }
36
96
  /**
37
97
  * Check if user is authenticated (without prompting)
38
98
  */
39
99
  export async function isAuthenticated() {
40
- const configManager = new GlobalConfigManager();
41
- return await configManager.isAuthenticated();
100
+ const profileManager = new ProfileManager();
101
+ const profile = await profileManager.getActiveProfile();
102
+ return profile !== null;
42
103
  }
43
104
  /**
44
105
  * Get stored email without prompting
45
106
  */
46
107
  export async function getStoredEmail() {
47
- const configManager = new GlobalConfigManager();
48
- return await configManager.getUserEmail();
108
+ const profileManager = new ProfileManager();
109
+ const profile = await profileManager.getActiveProfile();
110
+ return profile?.email || null;
49
111
  }
50
112
  /**
51
- * Clear authentication (logout)
113
+ * Clear authentication (logout from active profile)
52
114
  */
53
115
  export async function clearAuth() {
54
- const configManager = new GlobalConfigManager();
55
- await configManager.clearAuth();
56
- console.log(chalk.green('✅ Logged out successfully'));
116
+ const profileManager = new ProfileManager();
117
+ const profile = await profileManager.getActiveProfile();
118
+ if (profile) {
119
+ await profileManager.deleteProfile(profile.name);
120
+ console.log(chalk.green(`✓ Logged out from ${profile.email}`));
121
+ }
122
+ else {
123
+ console.log(chalk.yellow('Not logged in.'));
124
+ }
125
+ }
126
+ /**
127
+ * Get the current profile name (for display purposes)
128
+ */
129
+ export async function getCurrentProfileName() {
130
+ const profileManager = new ProfileManager();
131
+ const profile = await profileManager.getActiveProfile();
132
+ return profile?.name || null;
57
133
  }
58
134
  //# sourceMappingURL=auth-helper.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth-helper.js","sourceRoot":"","sources":["../../src/lib/auth-helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,KAAK,MAAM,OAAO,CAAC;AAO1B;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,cAAuB,KAAK;IAC7D,MAAM,aAAa,GAAG,IAAI,mBAAmB,EAAE,CAAC;IAEhD,uCAAuC;IACvC,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,aAAa,CAAC,SAAS,EAAE,CAAC;IAClC,CAAC;IAED,yBAAyB;IACzB,IAAI,KAAK,GAAG,MAAM,aAAa,CAAC,QAAQ,EAAE,CAAC;IAC3C,IAAI,KAAK,GAAG,MAAM,aAAa,CAAC,YAAY,EAAE,CAAC;IAE/C,sCAAsC;IACtC,IAAI,KAAK,IAAI,KAAK,EAAE,CAAC;QACnB,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IAC1B,CAAC;IAED,wCAAwC;IACxC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,4BAA4B,CAAC,CAAC,CAAC;IACxD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,iCAAiC,CAAC,CAAC,CAAC;IAE3D,6BAA6B;IAC7B,MAAM,UAAU,GAAG,MAAM,wBAAwB,CAAC,KAAK,IAAI,SAAS,CAAC,CAAC;IAEtE,qCAAqC;IACrC,MAAM,aAAa,CAAC,SAAS,CAC3B,UAAU,CAAC,KAAK,EAChB,UAAU,CAAC,KAAK,EAChB,IAAI,CAAC,wDAAwD;KAC9D,CAAC;IAEF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,sBAAsB,UAAU,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACnE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,oFAAoF,CAAC,CAAC,CAAC;IAE9G,OAAO;QACL,KAAK,EAAE,UAAU,CAAC,KAAK;QACvB,KAAK,EAAE,UAAU,CAAC,KAAK;KACxB,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,MAAM,aAAa,GAAG,IAAI,mBAAmB,EAAE,CAAC;IAChD,OAAO,MAAM,aAAa,CAAC,eAAe,EAAE,CAAC;AAC/C,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,aAAa,GAAG,IAAI,mBAAmB,EAAE,CAAC;IAChD,OAAO,MAAM,aAAa,CAAC,YAAY,EAAE,CAAC;AAC5C,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS;IAC7B,MAAM,aAAa,GAAG,IAAI,mBAAmB,EAAE,CAAC;IAChD,MAAM,aAAa,CAAC,SAAS,EAAE,CAAC;IAChC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC,CAAC;AACxD,CAAC"}
1
+ {"version":3,"file":"auth-helper.js","sourceRoot":"","sources":["../../src/lib/auth-helper.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Profile data structure for storing authentication tokens
3
+ */
4
+ export interface ProfileData {
5
+ name: string;
6
+ email: string;
7
+ userId: string;
8
+ accessToken: string;
9
+ refreshToken: string;
10
+ tokenExpiresAt: string;
11
+ createdAt: string;
12
+ lastUsedAt: string;
13
+ }
14
+ /**
15
+ * Global configuration structure
16
+ */
17
+ export interface GlobalConfig {
18
+ version: string;
19
+ defaultProfile: string;
20
+ migratedFrom?: string;
21
+ }
22
+ /**
23
+ * Project-level profile configuration
24
+ */
25
+ export interface ProjectProfileConfig {
26
+ profile?: string;
27
+ }
28
+ /**
29
+ * ProfileManager handles multi-profile authentication token storage and management.
30
+ *
31
+ * Storage structure:
32
+ * ~/.vibehub/
33
+ * ├── config.json # Global settings (defaultProfile, version)
34
+ * └── profiles/
35
+ * ├── default.json # Default profile tokens
36
+ * └── work.json # Named profile tokens
37
+ *
38
+ * Project-level override:
39
+ * project/.vibehub/config.json # Contains "profile" field to override default
40
+ */
41
+ export declare class ProfileManager {
42
+ private baseDir;
43
+ private configPath;
44
+ private profilesDir;
45
+ private supabase;
46
+ constructor();
47
+ /**
48
+ * Ensure the profile directories exist
49
+ */
50
+ private ensureDirectories;
51
+ /**
52
+ * Get the global configuration
53
+ */
54
+ getGlobalConfig(): Promise<GlobalConfig>;
55
+ /**
56
+ * Save the global configuration
57
+ */
58
+ saveGlobalConfig(config: GlobalConfig): Promise<void>;
59
+ /**
60
+ * Get a profile by name
61
+ */
62
+ getProfile(name: string): Promise<ProfileData | null>;
63
+ /**
64
+ * Save a profile
65
+ */
66
+ saveProfile(name: string, data: Omit<ProfileData, 'name'>): Promise<void>;
67
+ /**
68
+ * Delete a profile
69
+ */
70
+ deleteProfile(name: string): Promise<boolean>;
71
+ /**
72
+ * List all profiles
73
+ */
74
+ listProfiles(): Promise<ProfileData[]>;
75
+ /**
76
+ * Delete all profiles
77
+ */
78
+ deleteAllProfiles(): Promise<number>;
79
+ /**
80
+ * Get the project-level profile override (if any)
81
+ */
82
+ getProjectProfile(projectPath?: string): Promise<string | null>;
83
+ /**
84
+ * Set the profile for the current project
85
+ */
86
+ setProjectProfile(profileName: string, projectPath?: string): Promise<void>;
87
+ /**
88
+ * Clear the project profile override
89
+ */
90
+ clearProjectProfile(projectPath?: string): Promise<void>;
91
+ /**
92
+ * Get the active profile (project-level override or default)
93
+ */
94
+ getActiveProfile(projectPath?: string): Promise<ProfileData | null>;
95
+ /**
96
+ * Check if a token is expired or about to expire
97
+ */
98
+ isTokenExpired(profile: ProfileData, bufferMinutes?: number): boolean;
99
+ /**
100
+ * Refresh tokens if needed (silent refresh using refresh token)
101
+ */
102
+ refreshTokenIfNeeded(profile: ProfileData): Promise<ProfileData>;
103
+ /**
104
+ * Check if migration from old format is needed and perform it
105
+ */
106
+ migrateIfNeeded(): Promise<boolean>;
107
+ /**
108
+ * Get the profile status (for display)
109
+ */
110
+ getProfileStatus(profile: ProfileData): 'active' | 'expiring' | 'expired';
111
+ /**
112
+ * Get time until token expires (formatted string)
113
+ */
114
+ getTokenExpiryString(profile: ProfileData): string;
115
+ }
116
+ //# sourceMappingURL=profile-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile-manager.d.ts","sourceRoot":"","sources":["../../src/lib/profile-manager.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAMD;;;;;;;;;;;;GAYG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAiB;;IASjC;;OAEG;YACW,iBAAiB;IAK/B;;OAEG;IACG,eAAe,IAAI,OAAO,CAAC,YAAY,CAAC;IAe9C;;OAEG;IACG,gBAAgB,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAK3D;;OAEG;IACG,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAY3D;;OAEG;IACG,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB/E;;OAEG;IACG,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAiBnD;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAyB5C;;OAEG;IACG,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC;IAW1C;;OAEG;IACG,iBAAiB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IA2BrE;;OAEG;IACG,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBjF;;OAEG;IACG,mBAAmB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAe9D;;OAEG;IACG,gBAAgB,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAiBzE;;OAEG;IACH,cAAc,CAAC,OAAO,EAAE,WAAW,EAAE,aAAa,GAAE,MAAU,GAAG,OAAO;IAOxE;;OAEG;IACG,oBAAoB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IAmCtE;;OAEG;IACG,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC;IAsCzC;;OAEG;IACH,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS;IAazE;;OAEG;IACH,oBAAoB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM;CAqBnD"}
@@ -0,0 +1,351 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import { createClient } from '@supabase/supabase-js';
5
+ // Supabase configuration
6
+ const SUPABASE_URL = 'https://keogzaeakmndknpgrjif.supabase.co';
7
+ const SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imtlb2d6YWVha21uZGtucGdyamlmIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTc0MTYzMjQsImV4cCI6MjA3Mjk5MjMyNH0.38TymM-WiTS3ONu2KirEGbQT3XwRqSC5UBhZCQnlwO0';
8
+ /**
9
+ * ProfileManager handles multi-profile authentication token storage and management.
10
+ *
11
+ * Storage structure:
12
+ * ~/.vibehub/
13
+ * ├── config.json # Global settings (defaultProfile, version)
14
+ * └── profiles/
15
+ * ├── default.json # Default profile tokens
16
+ * └── work.json # Named profile tokens
17
+ *
18
+ * Project-level override:
19
+ * project/.vibehub/config.json # Contains "profile" field to override default
20
+ */
21
+ export class ProfileManager {
22
+ constructor() {
23
+ this.baseDir = path.join(os.homedir(), '.vibehub');
24
+ this.configPath = path.join(this.baseDir, 'config.json');
25
+ this.profilesDir = path.join(this.baseDir, 'profiles');
26
+ this.supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
27
+ }
28
+ /**
29
+ * Ensure the profile directories exist
30
+ */
31
+ async ensureDirectories() {
32
+ await fs.ensureDir(this.baseDir);
33
+ await fs.ensureDir(this.profilesDir);
34
+ }
35
+ /**
36
+ * Get the global configuration
37
+ */
38
+ async getGlobalConfig() {
39
+ try {
40
+ await this.ensureDirectories();
41
+ if (await fs.pathExists(this.configPath)) {
42
+ return await fs.readJson(this.configPath);
43
+ }
44
+ }
45
+ catch {
46
+ // Return default config if file doesn't exist or is invalid
47
+ }
48
+ return {
49
+ version: '2.0',
50
+ defaultProfile: 'default'
51
+ };
52
+ }
53
+ /**
54
+ * Save the global configuration
55
+ */
56
+ async saveGlobalConfig(config) {
57
+ await this.ensureDirectories();
58
+ await fs.writeJson(this.configPath, config, { spaces: 2 });
59
+ }
60
+ /**
61
+ * Get a profile by name
62
+ */
63
+ async getProfile(name) {
64
+ try {
65
+ const profilePath = path.join(this.profilesDir, `${name}.json`);
66
+ if (await fs.pathExists(profilePath)) {
67
+ return await fs.readJson(profilePath);
68
+ }
69
+ }
70
+ catch {
71
+ // Profile doesn't exist or is invalid
72
+ }
73
+ return null;
74
+ }
75
+ /**
76
+ * Save a profile
77
+ */
78
+ async saveProfile(name, data) {
79
+ await this.ensureDirectories();
80
+ const profilePath = path.join(this.profilesDir, `${name}.json`);
81
+ const profile = {
82
+ ...data,
83
+ name,
84
+ lastUsedAt: new Date().toISOString()
85
+ };
86
+ await fs.writeJson(profilePath, profile, { spaces: 2 });
87
+ // Update global config if this is the first profile
88
+ const config = await this.getGlobalConfig();
89
+ if (!config.defaultProfile || !(await this.getProfile(config.defaultProfile))) {
90
+ config.defaultProfile = name;
91
+ await this.saveGlobalConfig(config);
92
+ }
93
+ }
94
+ /**
95
+ * Delete a profile
96
+ */
97
+ async deleteProfile(name) {
98
+ const profilePath = path.join(this.profilesDir, `${name}.json`);
99
+ if (await fs.pathExists(profilePath)) {
100
+ await fs.remove(profilePath);
101
+ // If deleting the default profile, update config
102
+ const config = await this.getGlobalConfig();
103
+ if (config.defaultProfile === name) {
104
+ const profiles = await this.listProfiles();
105
+ config.defaultProfile = profiles.length > 0 ? profiles[0].name : 'default';
106
+ await this.saveGlobalConfig(config);
107
+ }
108
+ return true;
109
+ }
110
+ return false;
111
+ }
112
+ /**
113
+ * List all profiles
114
+ */
115
+ async listProfiles() {
116
+ try {
117
+ await this.ensureDirectories();
118
+ const files = await fs.readdir(this.profilesDir);
119
+ const profiles = [];
120
+ for (const file of files) {
121
+ if (file.endsWith('.json')) {
122
+ try {
123
+ const profile = await fs.readJson(path.join(this.profilesDir, file));
124
+ profiles.push(profile);
125
+ }
126
+ catch {
127
+ // Skip invalid profile files
128
+ }
129
+ }
130
+ }
131
+ return profiles.sort((a, b) => new Date(b.lastUsedAt).getTime() - new Date(a.lastUsedAt).getTime());
132
+ }
133
+ catch {
134
+ return [];
135
+ }
136
+ }
137
+ /**
138
+ * Delete all profiles
139
+ */
140
+ async deleteAllProfiles() {
141
+ const profiles = await this.listProfiles();
142
+ let count = 0;
143
+ for (const profile of profiles) {
144
+ if (await this.deleteProfile(profile.name)) {
145
+ count++;
146
+ }
147
+ }
148
+ return count;
149
+ }
150
+ /**
151
+ * Get the project-level profile override (if any)
152
+ */
153
+ async getProjectProfile(projectPath) {
154
+ try {
155
+ const searchPath = projectPath || process.cwd();
156
+ // Check for .vibehub/config.json in project
157
+ const projectConfigPath = path.join(searchPath, '.vibehub', 'config.json');
158
+ if (await fs.pathExists(projectConfigPath)) {
159
+ const projectConfig = await fs.readJson(projectConfigPath);
160
+ if (projectConfig.profile) {
161
+ return projectConfig.profile;
162
+ }
163
+ }
164
+ // Also check vibehub.json for backward compatibility
165
+ const vibehubConfigPath = path.join(searchPath, '.vibehub', 'vibehub.json');
166
+ if (await fs.pathExists(vibehubConfigPath)) {
167
+ const vibehubConfig = await fs.readJson(vibehubConfigPath);
168
+ if (vibehubConfig.profile) {
169
+ return vibehubConfig.profile;
170
+ }
171
+ }
172
+ }
173
+ catch {
174
+ // No project profile set
175
+ }
176
+ return null;
177
+ }
178
+ /**
179
+ * Set the profile for the current project
180
+ */
181
+ async setProjectProfile(profileName, projectPath) {
182
+ const searchPath = projectPath || process.cwd();
183
+ const configDir = path.join(searchPath, '.vibehub');
184
+ const configPath = path.join(configDir, 'config.json');
185
+ await fs.ensureDir(configDir);
186
+ // Load existing config or create new
187
+ let config = {};
188
+ try {
189
+ if (await fs.pathExists(configPath)) {
190
+ config = await fs.readJson(configPath);
191
+ }
192
+ }
193
+ catch {
194
+ // Start with empty config
195
+ }
196
+ config.profile = profileName;
197
+ await fs.writeJson(configPath, config, { spaces: 2 });
198
+ }
199
+ /**
200
+ * Clear the project profile override
201
+ */
202
+ async clearProjectProfile(projectPath) {
203
+ const searchPath = projectPath || process.cwd();
204
+ const configPath = path.join(searchPath, '.vibehub', 'config.json');
205
+ try {
206
+ if (await fs.pathExists(configPath)) {
207
+ const config = await fs.readJson(configPath);
208
+ delete config.profile;
209
+ await fs.writeJson(configPath, config, { spaces: 2 });
210
+ }
211
+ }
212
+ catch {
213
+ // No config to update
214
+ }
215
+ }
216
+ /**
217
+ * Get the active profile (project-level override or default)
218
+ */
219
+ async getActiveProfile(projectPath) {
220
+ // 1. Check for project-level override
221
+ const projectProfileName = await this.getProjectProfile(projectPath);
222
+ if (projectProfileName) {
223
+ const profile = await this.getProfile(projectProfileName);
224
+ if (profile) {
225
+ return profile;
226
+ }
227
+ // Project specifies a profile that doesn't exist
228
+ console.warn(`Warning: Project specifies profile '${projectProfileName}' but it doesn't exist.`);
229
+ }
230
+ // 2. Fall back to default profile
231
+ const config = await this.getGlobalConfig();
232
+ return await this.getProfile(config.defaultProfile);
233
+ }
234
+ /**
235
+ * Check if a token is expired or about to expire
236
+ */
237
+ isTokenExpired(profile, bufferMinutes = 5) {
238
+ const expiresAt = new Date(profile.tokenExpiresAt);
239
+ const now = new Date();
240
+ const bufferMs = bufferMinutes * 60 * 1000;
241
+ return expiresAt.getTime() - now.getTime() <= bufferMs;
242
+ }
243
+ /**
244
+ * Refresh tokens if needed (silent refresh using refresh token)
245
+ */
246
+ async refreshTokenIfNeeded(profile) {
247
+ if (!this.isTokenExpired(profile)) {
248
+ // Update last used time
249
+ profile.lastUsedAt = new Date().toISOString();
250
+ await this.saveProfile(profile.name, profile);
251
+ return profile;
252
+ }
253
+ // Token is expired or about to expire, refresh it
254
+ try {
255
+ const { data, error } = await this.supabase.auth.setSession({
256
+ access_token: profile.accessToken,
257
+ refresh_token: profile.refreshToken
258
+ });
259
+ if (error || !data.session) {
260
+ throw new Error(error?.message || 'Failed to refresh session');
261
+ }
262
+ // Update profile with new tokens
263
+ const updatedProfile = {
264
+ ...profile,
265
+ accessToken: data.session.access_token,
266
+ refreshToken: data.session.refresh_token || profile.refreshToken,
267
+ tokenExpiresAt: new Date(data.session.expires_at * 1000).toISOString(),
268
+ lastUsedAt: new Date().toISOString()
269
+ };
270
+ await this.saveProfile(profile.name, updatedProfile);
271
+ return updatedProfile;
272
+ }
273
+ catch (error) {
274
+ throw new Error(`Token refresh failed: ${error.message}. Please run 'vibe login' again.`);
275
+ }
276
+ }
277
+ /**
278
+ * Check if migration from old format is needed and perform it
279
+ */
280
+ async migrateIfNeeded() {
281
+ try {
282
+ // Check for old-style config with user.token
283
+ const oldConfigPath = path.join(this.baseDir, 'config.json');
284
+ if (!await fs.pathExists(oldConfigPath)) {
285
+ return false;
286
+ }
287
+ const oldConfig = await fs.readJson(oldConfigPath);
288
+ // Check if already migrated (has version 2.0)
289
+ if (oldConfig.version === '2.0') {
290
+ return false;
291
+ }
292
+ // Check for old user token format
293
+ if (oldConfig.user?.token) {
294
+ // Create backup
295
+ const backupPath = path.join(this.baseDir, 'config.backup.json');
296
+ await fs.copy(oldConfigPath, backupPath);
297
+ // Update config to new format
298
+ const newConfig = {
299
+ version: '2.0',
300
+ defaultProfile: 'default',
301
+ migratedFrom: '1.0'
302
+ };
303
+ await this.saveGlobalConfig(newConfig);
304
+ return true; // Migration needed - caller should prompt for re-authentication
305
+ }
306
+ return false;
307
+ }
308
+ catch {
309
+ return false;
310
+ }
311
+ }
312
+ /**
313
+ * Get the profile status (for display)
314
+ */
315
+ getProfileStatus(profile) {
316
+ const expiresAt = new Date(profile.tokenExpiresAt);
317
+ const now = new Date();
318
+ const diffMs = expiresAt.getTime() - now.getTime();
319
+ if (diffMs <= 0) {
320
+ return 'expired';
321
+ }
322
+ else if (diffMs <= 10 * 60 * 1000) { // 10 minutes
323
+ return 'expiring';
324
+ }
325
+ return 'active';
326
+ }
327
+ /**
328
+ * Get time until token expires (formatted string)
329
+ */
330
+ getTokenExpiryString(profile) {
331
+ const expiresAt = new Date(profile.tokenExpiresAt);
332
+ const now = new Date();
333
+ const diffMs = expiresAt.getTime() - now.getTime();
334
+ if (diffMs <= 0) {
335
+ return 'Expired';
336
+ }
337
+ const minutes = Math.floor(diffMs / 60000);
338
+ const hours = Math.floor(minutes / 60);
339
+ const days = Math.floor(hours / 24);
340
+ if (days > 0) {
341
+ return `${days} day${days > 1 ? 's' : ''}`;
342
+ }
343
+ else if (hours > 0) {
344
+ return `${hours} hour${hours > 1 ? 's' : ''}`;
345
+ }
346
+ else {
347
+ return `${minutes} minute${minutes > 1 ? 's' : ''}`;
348
+ }
349
+ }
350
+ }
351
+ //# sourceMappingURL=profile-manager.js.map