appwrite-utils-cli 1.6.3 → 1.6.5

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.
Files changed (59) hide show
  1. package/CONFIG_TODO.md +1189 -0
  2. package/SERVICE_IMPLEMENTATION_REPORT.md +462 -0
  3. package/dist/cli/commands/configCommands.js +7 -1
  4. package/dist/collections/attributes.js +102 -30
  5. package/dist/collections/indexes.js +6 -1
  6. package/dist/collections/methods.js +4 -5
  7. package/dist/config/ConfigManager.d.ts +445 -0
  8. package/dist/config/ConfigManager.js +625 -0
  9. package/dist/config/index.d.ts +8 -0
  10. package/dist/config/index.js +7 -0
  11. package/dist/config/services/ConfigDiscoveryService.d.ts +126 -0
  12. package/dist/config/services/ConfigDiscoveryService.js +374 -0
  13. package/dist/config/services/ConfigLoaderService.d.ts +105 -0
  14. package/dist/config/services/ConfigLoaderService.js +410 -0
  15. package/dist/config/services/ConfigMergeService.d.ts +208 -0
  16. package/dist/config/services/ConfigMergeService.js +307 -0
  17. package/dist/config/services/ConfigValidationService.d.ts +214 -0
  18. package/dist/config/services/ConfigValidationService.js +310 -0
  19. package/dist/config/services/SessionAuthService.d.ts +225 -0
  20. package/dist/config/services/SessionAuthService.js +456 -0
  21. package/dist/config/services/__tests__/ConfigMergeService.test.d.ts +1 -0
  22. package/dist/config/services/__tests__/ConfigMergeService.test.js +271 -0
  23. package/dist/config/services/index.d.ts +13 -0
  24. package/dist/config/services/index.js +10 -0
  25. package/dist/interactiveCLI.js +8 -6
  26. package/dist/main.js +14 -19
  27. package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +1 -1
  28. package/dist/shared/operationQueue.js +1 -1
  29. package/dist/utils/ClientFactory.d.ts +87 -0
  30. package/dist/utils/ClientFactory.js +164 -0
  31. package/dist/utils/getClientFromConfig.js +4 -3
  32. package/dist/utils/helperFunctions.d.ts +1 -0
  33. package/dist/utils/helperFunctions.js +21 -5
  34. package/dist/utils/yamlConverter.d.ts +2 -2
  35. package/dist/utils/yamlConverter.js +2 -2
  36. package/dist/utilsController.d.ts +18 -15
  37. package/dist/utilsController.js +83 -131
  38. package/package.json +1 -1
  39. package/src/cli/commands/configCommands.ts +8 -1
  40. package/src/collections/attributes.ts +118 -31
  41. package/src/collections/indexes.ts +7 -1
  42. package/src/collections/methods.ts +4 -6
  43. package/src/config/ConfigManager.ts +808 -0
  44. package/src/config/index.ts +10 -0
  45. package/src/config/services/ConfigDiscoveryService.ts +463 -0
  46. package/src/config/services/ConfigLoaderService.ts +560 -0
  47. package/src/config/services/ConfigMergeService.ts +386 -0
  48. package/src/config/services/ConfigValidationService.ts +394 -0
  49. package/src/config/services/SessionAuthService.ts +565 -0
  50. package/src/config/services/__tests__/ConfigMergeService.test.ts +351 -0
  51. package/src/config/services/index.ts +29 -0
  52. package/src/interactiveCLI.ts +9 -7
  53. package/src/main.ts +14 -24
  54. package/src/shared/operationQueue.ts +1 -1
  55. package/src/utils/ClientFactory.ts +186 -0
  56. package/src/utils/getClientFromConfig.ts +4 -3
  57. package/src/utils/helperFunctions.ts +27 -7
  58. package/src/utils/yamlConverter.ts +4 -4
  59. package/src/utilsController.ts +99 -187
@@ -4,6 +4,7 @@ import { AdapterFactory, type AdapterFactoryResult } from "../adapters/AdapterFa
4
4
  import type { DatabaseAdapter } from "../adapters/DatabaseAdapter.js";
5
5
  import { findSessionByEndpointAndProject, hasSessionAuth, isValidSessionCookie } from "./sessionAuth.js";
6
6
  import { MessageFormatter } from "../shared/messageFormatter.js";
7
+ import { logger } from "../shared/logging.js";
7
8
 
8
9
  /**
9
10
  * Enhanced client creation from config with session authentication support
@@ -43,7 +44,7 @@ export const getClientWithAuth = (
43
44
  if (sessionCookie) {
44
45
  if (isValidSessionCookie(sessionCookie)) {
45
46
  client.setSession(sessionCookie);
46
- MessageFormatter.info(`Using explicit session authentication for project ${project}`, { prefix: "Auth" });
47
+ logger.debug("Using explicit session authentication", { prefix: "Auth", project });
47
48
  return client;
48
49
  } else {
49
50
  authAttempts.push("explicit session cookie (invalid format)");
@@ -56,7 +57,7 @@ export const getClientWithAuth = (
56
57
  if (sessionAuth) {
57
58
  if (isValidSessionCookie(sessionAuth.sessionCookie)) {
58
59
  client.setSession(sessionAuth.sessionCookie);
59
- MessageFormatter.info(`Using session authentication for project ${project} (${sessionAuth.email || 'unknown user'})`, { prefix: "Auth" });
60
+ logger.debug("Using session authentication", { prefix: "Auth", project, email: sessionAuth.email || 'unknown user' });
60
61
  return client;
61
62
  } else {
62
63
  authAttempts.push("session from CLI prefs (invalid/expired)");
@@ -70,7 +71,7 @@ export const getClientWithAuth = (
70
71
  authAttempts.push("API key (empty)");
71
72
  } else {
72
73
  client.setKey(key);
73
- MessageFormatter.info(`Using API key authentication for project ${project}`, { prefix: "Auth" });
74
+ logger.debug("Using API key authentication", { prefix: "Auth", project });
74
75
  return client;
75
76
  }
76
77
  }
@@ -135,6 +135,7 @@ export let numTimesFailedTotal = 0;
135
135
 
136
136
  /**
137
137
  * Tries to execute the given createFunction and retries up to 5 times if it fails.
138
+ * Only retries on transient errors (network failures, 5xx errors). Does NOT retry validation errors (4xx).
138
139
  *
139
140
  * @param {() => Promise<any>} createFunction - The function to be executed.
140
141
  * @param {number} [attemptNum=0] - The number of attempts made so far (default: 0).
@@ -148,13 +149,30 @@ export const tryAwaitWithRetry = async <T>(
148
149
  try {
149
150
  return await createFunction();
150
151
  } catch (error) {
151
- if (
152
- (error instanceof AppwriteException &&
153
- (error.message.toLowerCase().includes("fetch failed") ||
154
- error.message.toLowerCase().includes("server error"))) ||
155
- ((error as any).code === 522 || (error as any).code === "522")
156
- ) {
157
- if ((error as any).code === 522) {
152
+ const errorMessage = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
153
+ const errorCode = (error as any).code;
154
+
155
+ // Check if this is a validation error that should NOT be retried
156
+ const isValidationError =
157
+ errorCode === 400 || errorCode === 409 || errorCode === 422 ||
158
+ errorMessage.includes("already exists") ||
159
+ errorMessage.includes("attribute with the same key") ||
160
+ errorMessage.includes("invalid") && !errorMessage.includes("fetch failed") ||
161
+ errorMessage.includes("conflict") ||
162
+ errorMessage.includes("bad request");
163
+
164
+ // Check if this is a transient error that SHOULD be retried
165
+ const isTransientError =
166
+ errorCode === 522 || errorCode === "522" || // Cloudflare error
167
+ errorCode >= 500 && errorCode < 600 || // 5xx server errors
168
+ errorMessage.includes("fetch failed") || // Network failures
169
+ errorMessage.includes("timeout") ||
170
+ errorMessage.includes("econnrefused") ||
171
+ errorMessage.includes("network error");
172
+
173
+ // Only retry if it's a transient error AND not a validation error
174
+ if (isTransientError && !isValidationError) {
175
+ if (errorCode === 522 || errorCode === "522") {
158
176
  console.log("Cloudflare error. Retrying...");
159
177
  } else {
160
178
  console.log(`Fetch failed on attempt ${attemptNum}. Retrying...`);
@@ -166,6 +184,8 @@ export const tryAwaitWithRetry = async <T>(
166
184
  await delay(2500);
167
185
  return tryAwaitWithRetry(createFunction, attemptNum + 1);
168
186
  }
187
+
188
+ // For validation errors or non-transient errors, throw immediately
169
189
  if (throwError) {
170
190
  throw error;
171
191
  }
@@ -19,7 +19,7 @@ export interface YamlCollectionData {
19
19
  size?: number;
20
20
  required?: boolean;
21
21
  array?: boolean;
22
- encrypt?: boolean;
22
+ encrypted?: boolean;
23
23
  default?: any;
24
24
  min?: number;
25
25
  max?: number;
@@ -38,7 +38,7 @@ export interface YamlCollectionData {
38
38
  size?: number;
39
39
  required?: boolean;
40
40
  array?: boolean;
41
- encrypt?: boolean;
41
+ encrypted?: boolean;
42
42
  default?: any;
43
43
  min?: number;
44
44
  max?: number;
@@ -111,9 +111,9 @@ export function collectionToYaml(
111
111
  if (attr.required !== undefined) yamlAttr.required = attr.required;
112
112
  if (attr.array !== undefined) yamlAttr.array = attr.array;
113
113
 
114
- // Always include encrypt field for string attributes (default to false)
114
+ // Always include encrypted field for string attributes (default to false)
115
115
  if (attr.type === 'string') {
116
- yamlAttr.encrypt = ('encrypted' in attr && attr.encrypted === true) ? true : false;
116
+ yamlAttr.encrypted = ('encrypted' in attr && attr.encrypted === true) ? true : false;
117
117
  }
118
118
 
119
119
  if ('xdefault' in attr && attr.xdefault !== undefined) yamlAttr.default = attr.xdefault;
@@ -60,7 +60,6 @@ import { getClient, getClientWithAuth } from "./utils/getClientFromConfig.js";
60
60
  import { getAdapterFromConfig } from "./utils/getClientFromConfig.js";
61
61
  import type { DatabaseAdapter } from './adapters/DatabaseAdapter.js';
62
62
  import { hasSessionAuth, findSessionByEndpointAndProject, isValidSessionCookie, type SessionAuthInfo } from "./utils/sessionAuth.js";
63
- import { createSessionPreservation, type SessionPreservationOptions } from "./utils/loadConfigs.js";
64
63
  import { fetchAllDatabases } from "./databases/methods.js";
65
64
  import {
66
65
  listFunctions,
@@ -69,7 +68,7 @@ import {
69
68
  import chalk from "chalk";
70
69
  import { deployLocalFunction } from "./functions/deployments.js";
71
70
  import fs from "node:fs";
72
- import { configureLogging, updateLogger } from "./shared/logging.js";
71
+ import { configureLogging, updateLogger, logger } from "./shared/logging.js";
73
72
  import { MessageFormatter, Messages } from "./shared/messageFormatter.js";
74
73
  import { SchemaGenerator } from "./shared/schemaGenerator.js";
75
74
  import { findYamlConfig } from "./config/yamlConfig.js";
@@ -79,6 +78,8 @@ import {
79
78
  validateWithStrictMode,
80
79
  type ValidationResult
81
80
  } from "./config/configValidation.js";
81
+ import { ConfigManager } from "./config/ConfigManager.js";
82
+ import { ClientFactory } from "./utils/ClientFactory.js";
82
83
 
83
84
  export interface SetupOptions {
84
85
  databases?: Models.Database[];
@@ -96,8 +97,42 @@ export interface SetupOptions {
96
97
  }
97
98
 
98
99
  export class UtilsController {
100
+ // ──────────────────────────────────────────────────
101
+ // SINGLETON PATTERN
102
+ // ──────────────────────────────────────────────────
103
+ private static instance: UtilsController | null = null;
104
+ private isInitialized: boolean = false;
105
+
106
+ /**
107
+ * Get the UtilsController singleton instance
108
+ */
109
+ public static getInstance(
110
+ currentUserDir: string,
111
+ directConfig?: {
112
+ appwriteEndpoint?: string;
113
+ appwriteProject?: string;
114
+ appwriteKey?: string;
115
+ }
116
+ ): UtilsController {
117
+ if (!UtilsController.instance) {
118
+ UtilsController.instance = new UtilsController(currentUserDir, directConfig);
119
+ }
120
+ return UtilsController.instance;
121
+ }
122
+
123
+ /**
124
+ * Clear the singleton instance (useful for testing)
125
+ */
126
+ public static clearInstance(): void {
127
+ UtilsController.instance = null;
128
+ }
129
+
130
+ // ──────────────────────────────────────────────────
131
+ // INSTANCE FIELDS
132
+ // ──────────────────────────────────────────────────
99
133
  private appwriteFolderPath?: string;
100
134
  private appwriteConfigPath?: string;
135
+ private currentUserDir: string;
101
136
  public config?: AppwriteConfig;
102
137
  public appwriteServer?: Client;
103
138
  public database?: Databases;
@@ -107,14 +142,6 @@ export class UtilsController {
107
142
  public validityRuleDefinitions: ValidationRules = validationRules;
108
143
  public afterImportActionsDefinitions: AfterImportActions = afterImportActions;
109
144
 
110
- // Session preservation fields
111
- private sessionCookie?: string;
112
- private authMethod?: "session" | "apikey" | "auto";
113
- private sessionMetadata?: {
114
- email?: string;
115
- expiresAt?: string;
116
- };
117
-
118
145
  constructor(
119
146
  currentUserDir: string,
120
147
  directConfig?: {
@@ -123,6 +150,7 @@ export class UtilsController {
123
150
  appwriteKey?: string;
124
151
  }
125
152
  ) {
153
+ this.currentUserDir = currentUserDir;
126
154
  const basePath = currentUserDir;
127
155
 
128
156
  if (directConfig) {
@@ -194,136 +222,69 @@ export class UtilsController {
194
222
  }
195
223
 
196
224
  async init(options: { validate?: boolean; strictMode?: boolean; useSession?: boolean; sessionCookie?: string } = {}) {
197
- const { validate = false, strictMode = false, useSession = false, sessionCookie } = options;
198
-
199
- if (!this.config) {
200
- if (this.appwriteFolderPath && this.appwriteConfigPath) {
201
- MessageFormatter.progress("Loading config from file...", { prefix: "Config" });
202
- try {
203
- const { config, actualConfigPath, validation } = await loadConfigWithPath(
204
- this.appwriteFolderPath,
205
- { validate, strictMode, reportValidation: false }
206
- );
207
- this.config = config;
208
- MessageFormatter.info(`Loaded config from: ${actualConfigPath}`, { prefix: "Config" });
209
-
210
- // Report validation results if validation was requested
211
- if (validation && validate) {
212
- reportValidationResults(validation, { verbose: false });
213
-
214
- // In strict mode, throw if validation fails
215
- if (strictMode && !validation.isValid) {
216
- throw new Error(`Configuration validation failed in strict mode. Found ${validation.errors.length} validation errors.`);
217
- }
218
- }
219
- } catch (error) {
220
- MessageFormatter.error("Failed to load config from file", error instanceof Error ? error : undefined, { prefix: "Config" });
221
- return;
222
- }
223
- } else {
224
- MessageFormatter.error("No configuration available", undefined, { prefix: "Config" });
225
- return;
226
- }
225
+ const { validate = false, strictMode = false } = options;
226
+ const configManager = ConfigManager.getInstance();
227
+
228
+ // Load config if not already loaded
229
+ if (!configManager.hasConfig()) {
230
+ await configManager.loadConfig({
231
+ configDir: this.currentUserDir,
232
+ validate,
233
+ strictMode,
234
+ });
227
235
  }
228
236
 
237
+ const config = configManager.getConfig();
238
+
229
239
  // Configure logging based on config
230
- if (this.config.logging) {
231
- configureLogging(this.config.logging);
240
+ if (config.logging) {
241
+ configureLogging(config.logging);
232
242
  updateLogger();
233
243
  }
234
244
 
235
- // Use enhanced client with session authentication support
236
- // Pass session cookie from options if provided
237
- const clientSessionCookie = sessionCookie || this.sessionCookie;
238
- this.appwriteServer = getClientWithAuth(
239
- this.config.appwriteEndpoint,
240
- this.config.appwriteProject,
241
- this.config.appwriteKey || undefined,
242
- clientSessionCookie
243
- );
245
+ // Create client and adapter (session already in config from ConfigManager)
246
+ const { client, adapter } = await ClientFactory.createFromConfig(config);
244
247
 
248
+ this.appwriteServer = client;
249
+ this.adapter = adapter;
250
+ this.config = config;
245
251
  this.database = new Databases(this.appwriteServer);
246
252
  this.storage = new Storage(this.appwriteServer);
247
253
  this.config.appwriteClient = this.appwriteServer;
248
254
 
249
- // Initialize adapter with version detection
250
- try {
251
- const { adapter, apiMode } = await getAdapterFromConfig(
252
- this.config,
253
- false,
254
- clientSessionCookie
255
- );
256
- this.adapter = adapter;
257
-
258
- MessageFormatter.info(`Database adapter initialized (apiMode: ${apiMode})`, {
259
- prefix: "Adapter"
260
- });
261
- } catch (error) {
262
- MessageFormatter.warning(
263
- 'Database adapter initialization failed - some features may not work',
264
- { prefix: "Adapter" }
265
- );
255
+ // Log only on FIRST initialization to avoid spam
256
+ if (!this.isInitialized) {
257
+ const apiMode = adapter.getApiMode();
258
+ MessageFormatter.info(`Database adapter initialized (apiMode: ${apiMode})`, { prefix: "Adapter" });
259
+ this.isInitialized = true;
260
+ } else {
261
+ logger.debug("Adapter reused from cache", { prefix: "UtilsController" });
266
262
  }
267
-
268
- // Extract and store session information after successful authentication
269
- this.extractSessionInfo();
270
263
  }
271
264
 
272
265
  async reloadConfig() {
273
- if (!this.appwriteFolderPath) {
274
- MessageFormatter.error("Failed to get appwriteFolderPath", undefined, { prefix: "Controller" });
275
- return;
276
- }
277
-
278
- // Preserve session authentication during config reload
279
- const preserveAuth = this.createSessionPreservationOptions();
266
+ const configManager = ConfigManager.getInstance();
280
267
 
281
- this.config = await loadConfig(this.appwriteFolderPath, {
282
- preserveAuth
283
- });
284
- if (!this.config) {
285
- MessageFormatter.error("Failed to load config", undefined, { prefix: "Controller" });
286
- return;
287
- }
268
+ // Session preservation is automatic in ConfigManager
269
+ const config = await configManager.reloadConfig();
288
270
 
289
271
  // Configure logging based on updated config
290
- if (this.config.logging) {
291
- configureLogging(this.config.logging);
272
+ if (config.logging) {
273
+ configureLogging(config.logging);
292
274
  updateLogger();
293
275
  }
294
276
 
295
- // Use enhanced client with session authentication support, passing preserved session
296
- this.appwriteServer = getClientWithAuth(
297
- this.config.appwriteEndpoint,
298
- this.config.appwriteProject,
299
- this.config.appwriteKey || undefined,
300
- this.sessionCookie
301
- );
277
+ // Recreate client and adapter
278
+ const { client, adapter } = await ClientFactory.createFromConfig(config);
279
+
280
+ this.appwriteServer = client;
281
+ this.adapter = adapter;
282
+ this.config = config;
302
283
  this.database = new Databases(this.appwriteServer);
303
284
  this.storage = new Storage(this.appwriteServer);
304
285
  this.config.appwriteClient = this.appwriteServer;
305
286
 
306
- // Re-initialize adapter with version detection after config reload
307
- try {
308
- const { adapter, apiMode } = await getAdapterFromConfig(
309
- this.config,
310
- false,
311
- this.sessionCookie
312
- );
313
- this.adapter = adapter;
314
-
315
- MessageFormatter.info(`Database adapter re-initialized (apiMode: ${apiMode})`, {
316
- prefix: "Adapter"
317
- });
318
- } catch (error) {
319
- MessageFormatter.warning(
320
- 'Database adapter re-initialization failed - some features may not work',
321
- { prefix: "Adapter" }
322
- );
323
- }
324
-
325
- // Re-extract session information after reload
326
- this.extractSessionInfo();
287
+ logger.debug("Config reloaded, adapter refreshed", { prefix: "UtilsController" });
327
288
  }
328
289
 
329
290
 
@@ -510,7 +471,8 @@ export class UtilsController {
510
471
  await this.init();
511
472
  if (!this.database || !this.config) throw new Error("Database not initialized");
512
473
  try {
513
- const { adapter, apiMode } = await getAdapterFromConfig(this.config, false, this.sessionCookie);
474
+ // Session is already in config from ConfigManager
475
+ const { adapter, apiMode } = await getAdapterFromConfig(this.config, false);
514
476
  if (apiMode === 'tablesdb') {
515
477
  await wipeAllTables(adapter, database.$id);
516
478
  } else {
@@ -557,7 +519,8 @@ export class UtilsController {
557
519
  await this.init();
558
520
  if (!this.database || !this.config) throw new Error("Database not initialized");
559
521
  try {
560
- const { adapter, apiMode } = await getAdapterFromConfig(this.config, false, this.sessionCookie);
522
+ // Session is already in config from ConfigManager
523
+ const { adapter, apiMode } = await getAdapterFromConfig(this.config, false);
561
524
  if (apiMode === 'tablesdb') {
562
525
  await wipeTableRows(adapter, database.$id, collection.$id);
563
526
  } else {
@@ -915,82 +878,31 @@ export class UtilsController {
915
878
  return validation;
916
879
  }
917
880
 
918
- /**
919
- * Extract session information from the current authenticated client
920
- * This preserves session context for use across config reloads and adapter operations
921
- */
922
- private extractSessionInfo(): void {
923
- if (!this.config) {
924
- return;
925
- }
926
-
927
- // Try to extract session from current config first
928
- if (this.config.sessionCookie && isValidSessionCookie(this.config.sessionCookie)) {
929
- this.sessionCookie = this.config.sessionCookie;
930
- this.authMethod = "session";
931
- this.sessionMetadata = this.config.sessionMetadata;
932
- MessageFormatter.debug("Extracted session from config", { prefix: "Session" });
933
- return;
934
- }
935
-
936
- // Fall back to finding session from Appwrite CLI preferences
937
- const sessionAuth = findSessionByEndpointAndProject(
938
- this.config.appwriteEndpoint,
939
- this.config.appwriteProject
940
- );
941
-
942
- if (sessionAuth && isValidSessionCookie(sessionAuth.sessionCookie)) {
943
- this.sessionCookie = sessionAuth.sessionCookie;
944
- this.authMethod = "session";
945
- this.sessionMetadata = {
946
- email: sessionAuth.email
947
- };
948
- MessageFormatter.debug(
949
- `Extracted session from CLI preferences for ${sessionAuth.email || 'unknown user'}`,
950
- { prefix: "Session" }
951
- );
952
- return;
953
- }
954
-
955
- // No session found, using API key authentication
956
- if (this.config.appwriteKey) {
957
- this.authMethod = "apikey";
958
- this.sessionCookie = undefined;
959
- this.sessionMetadata = undefined;
960
- MessageFormatter.debug("Using API key authentication", { prefix: "Session" });
961
- }
962
- }
963
-
964
- /**
965
- * Create session preservation options for passing to loadConfig
966
- * Returns current session state to maintain authentication across config reloads
967
- */
968
- private createSessionPreservationOptions(): SessionPreservationOptions | undefined {
969
- if (!this.sessionCookie || !isValidSessionCookie(this.sessionCookie)) {
970
- return undefined;
971
- }
972
-
973
- return createSessionPreservation(
974
- this.sessionCookie,
975
- this.sessionMetadata?.email,
976
- this.sessionMetadata?.expiresAt
977
- );
978
- }
979
-
980
881
  /**
981
882
  * Get current session information for debugging/logging purposes
883
+ * Delegates to ConfigManager for session info
982
884
  */
983
- public getSessionInfo(): {
885
+ public async getSessionInfo(): Promise<{
984
886
  hasSession: boolean;
985
887
  authMethod?: string;
986
888
  email?: string;
987
889
  expiresAt?: string;
988
- } {
989
- return {
990
- hasSession: !!this.sessionCookie && isValidSessionCookie(this.sessionCookie),
991
- authMethod: this.authMethod,
992
- email: this.sessionMetadata?.email,
993
- expiresAt: this.sessionMetadata?.expiresAt
994
- };
890
+ }> {
891
+ const configManager = ConfigManager.getInstance();
892
+
893
+ try {
894
+ const authStatus = await configManager.getAuthStatus();
895
+ return {
896
+ hasSession: authStatus.hasValidSession,
897
+ authMethod: authStatus.authMethod,
898
+ email: authStatus.sessionInfo?.email,
899
+ expiresAt: authStatus.sessionInfo?.expiresAt
900
+ };
901
+ } catch (error) {
902
+ // If config not loaded, return empty status
903
+ return {
904
+ hasSession: false
905
+ };
906
+ }
995
907
  }
996
908
  }