berget 1.1.0 → 1.3.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/-27b-it +0 -0
- package/dist/index.js +47 -1
- package/dist/package.json +35 -0
- package/dist/src/client.js +55 -48
- package/dist/src/commands/api-keys.js +13 -7
- package/dist/src/commands/chat.js +100 -18
- package/dist/src/services/api-key-service.js +6 -16
- package/dist/src/services/chat-service.js +284 -56
- package/dist/src/utils/default-api-key.js +115 -6
- package/dist/src/utils/error-handler.js +4 -4
- package/dist/src/utils/logger.js +160 -0
- package/dist/src/utils/token-manager.js +6 -9
- package/index.ts +52 -1
- package/package.json +3 -2
- package/src/client.ts +83 -81
- package/src/commands/api-keys.ts +17 -7
- package/src/commands/chat.ts +142 -22
- package/src/services/api-key-service.ts +12 -20
- package/src/services/chat-service.ts +407 -87
- package/src/types/api.d.ts +203 -9
- package/src/types/json.d.ts +4 -0
- package/src/utils/default-api-key.ts +124 -6
- package/src/utils/error-handler.ts +4 -4
- package/src/utils/logger.ts +159 -0
- package/src/utils/token-manager.ts +6 -5
- package/tsconfig.json +1 -1
package/src/types/api.d.ts
CHANGED
|
@@ -14,6 +14,20 @@ export interface paths {
|
|
|
14
14
|
/**
|
|
15
15
|
* List all API keys
|
|
16
16
|
* @description Lists all API keys for the authenticated user's organization.
|
|
17
|
+
*
|
|
18
|
+
* ## Using the CLI
|
|
19
|
+
*
|
|
20
|
+
* You can also manage API keys using our CLI tool:
|
|
21
|
+
* ```
|
|
22
|
+
* # Login first (if you haven't already)
|
|
23
|
+
* npx berget auth login
|
|
24
|
+
*
|
|
25
|
+
* # List all API keys
|
|
26
|
+
* npx berget api-keys list
|
|
27
|
+
*
|
|
28
|
+
* # Create a new API key
|
|
29
|
+
* npx berget api-keys create --name my-api-key
|
|
30
|
+
* ```
|
|
17
31
|
*/
|
|
18
32
|
get: {
|
|
19
33
|
responses: {
|
|
@@ -36,6 +50,17 @@ export interface paths {
|
|
|
36
50
|
/**
|
|
37
51
|
* Create a new API key
|
|
38
52
|
* @description Creates a new API key for the authenticated user's organization. The full API key is only returned once at creation time.
|
|
53
|
+
*
|
|
54
|
+
* ## Using the CLI
|
|
55
|
+
*
|
|
56
|
+
* Creating an API key is easier with our CLI tool:
|
|
57
|
+
* ```
|
|
58
|
+
* # Login first (if you haven't already)
|
|
59
|
+
* npx berget auth login
|
|
60
|
+
*
|
|
61
|
+
* # Create a new API key
|
|
62
|
+
* npx berget api-keys create --name my-api-key
|
|
63
|
+
* ```
|
|
39
64
|
*/
|
|
40
65
|
post: {
|
|
41
66
|
requestBody: {
|
|
@@ -97,6 +122,21 @@ export interface paths {
|
|
|
97
122
|
/**
|
|
98
123
|
* Rotate an API key
|
|
99
124
|
* @description Rotates an API key by invalidating the old key and generating a new one. The new key is returned in the response and is only shown once.
|
|
125
|
+
*
|
|
126
|
+
* ## Using the CLI
|
|
127
|
+
*
|
|
128
|
+
* You can also rotate API keys using our CLI tool:
|
|
129
|
+
* ```
|
|
130
|
+
* # Login first (if you haven't already)
|
|
131
|
+
* npx berget auth login
|
|
132
|
+
*
|
|
133
|
+
* # Rotate an API key
|
|
134
|
+
* npx berget api-keys rotate --id <key-id>
|
|
135
|
+
* ```
|
|
136
|
+
*
|
|
137
|
+
* ## Security Note
|
|
138
|
+
*
|
|
139
|
+
* When you rotate an API key, the old key becomes invalid immediately. Make sure to update any applications using the key.
|
|
100
140
|
*/
|
|
101
141
|
put: {
|
|
102
142
|
parameters: {
|
|
@@ -131,6 +171,20 @@ export interface paths {
|
|
|
131
171
|
/**
|
|
132
172
|
* Get API key usage statistics
|
|
133
173
|
* @description Returns usage statistics for a specific API key including request count, daily breakdown, model-specific usage, and token consumption.
|
|
174
|
+
*
|
|
175
|
+
* ## Using the CLI
|
|
176
|
+
*
|
|
177
|
+
* You can also view API key usage using our CLI tool:
|
|
178
|
+
* ```
|
|
179
|
+
* # Login first (if you haven't already)
|
|
180
|
+
* npx berget auth login
|
|
181
|
+
*
|
|
182
|
+
* # View usage for a specific API key
|
|
183
|
+
* npx berget api-keys usage --id <key-id>
|
|
184
|
+
*
|
|
185
|
+
* # View usage for all API keys
|
|
186
|
+
* npx berget usage
|
|
187
|
+
* ```
|
|
134
188
|
*/
|
|
135
189
|
get: {
|
|
136
190
|
parameters: {
|
|
@@ -351,13 +405,27 @@ export interface paths {
|
|
|
351
405
|
"/v1/auth/login": {
|
|
352
406
|
/**
|
|
353
407
|
* OAuth login
|
|
354
|
-
* @description Initiates OAuth login flow via Keycloak
|
|
408
|
+
* @description Initiates OAuth login flow via Keycloak.
|
|
409
|
+
*
|
|
410
|
+
* ## CLI Authentication
|
|
411
|
+
*
|
|
412
|
+
* For a simpler experience, you can use our CLI tool to authenticate:
|
|
413
|
+
* ```
|
|
414
|
+
* npx berget auth login
|
|
415
|
+
* ```
|
|
416
|
+
*
|
|
417
|
+
* After logging in, you can create an API key with:
|
|
418
|
+
* ```
|
|
419
|
+
* npx berget api-keys create --name my-api-key
|
|
420
|
+
* ```
|
|
355
421
|
*/
|
|
356
422
|
get: {
|
|
357
423
|
parameters: {
|
|
358
424
|
query?: {
|
|
359
425
|
/** @description URL to redirect to after successful login */
|
|
360
426
|
redirect_uri?: string;
|
|
427
|
+
/** @description How to return the token after successful login (default is redirect) */
|
|
428
|
+
response_type?: "redirect" | "json";
|
|
361
429
|
};
|
|
362
430
|
};
|
|
363
431
|
responses: {
|
|
@@ -389,29 +457,108 @@ export interface paths {
|
|
|
389
457
|
};
|
|
390
458
|
};
|
|
391
459
|
"/v1/auth/device": {
|
|
392
|
-
/**
|
|
460
|
+
/**
|
|
461
|
+
* Initiate device authorization flow
|
|
462
|
+
* @description Initiates the device authorization flow, returning a device code and user verification URL.
|
|
463
|
+
*
|
|
464
|
+
* ## Using the CLI
|
|
465
|
+
*
|
|
466
|
+
* The recommended way to authenticate is through our CLI tool:
|
|
467
|
+
* ```
|
|
468
|
+
* npx berget auth login
|
|
469
|
+
* ```
|
|
470
|
+
*
|
|
471
|
+
* This handles the device flow automatically and provides a better user experience.
|
|
472
|
+
*/
|
|
393
473
|
post: {
|
|
394
474
|
responses: {
|
|
395
475
|
/** @description Device authorization initiated */
|
|
396
476
|
200: {
|
|
397
|
-
content:
|
|
477
|
+
content: {
|
|
478
|
+
"application/json": components["schemas"]["DeviceAuthInitResponse"];
|
|
479
|
+
};
|
|
398
480
|
};
|
|
399
481
|
};
|
|
400
482
|
};
|
|
401
483
|
};
|
|
402
484
|
"/v1/auth/device/token": {
|
|
403
|
-
/**
|
|
485
|
+
/**
|
|
486
|
+
* Poll for device token
|
|
487
|
+
* @description Polls for the status of a device authorization flow. The client should poll this endpoint
|
|
488
|
+
* until it receives a token or an error.
|
|
489
|
+
*
|
|
490
|
+
* ## Using the CLI
|
|
491
|
+
*
|
|
492
|
+
* The recommended way to authenticate is through our CLI tool:
|
|
493
|
+
* ```
|
|
494
|
+
* npx berget auth login
|
|
495
|
+
* ```
|
|
496
|
+
*
|
|
497
|
+
* This handles the polling automatically and provides a better user experience.
|
|
498
|
+
*
|
|
499
|
+
* ## Troubleshooting
|
|
500
|
+
*
|
|
501
|
+
* - If you receive a 400 error, the device code may be invalid or expired
|
|
502
|
+
* - If you receive a 429 error, you're polling too frequently
|
|
503
|
+
* - If you receive a 500 error, there may be an issue with the authentication service
|
|
504
|
+
*/
|
|
404
505
|
post: {
|
|
405
506
|
requestBody: {
|
|
406
507
|
content: {
|
|
407
|
-
"application/json":
|
|
408
|
-
device_code?: string;
|
|
409
|
-
};
|
|
508
|
+
"application/json": components["schemas"]["DeviceAuthRequest"];
|
|
410
509
|
};
|
|
411
510
|
};
|
|
412
511
|
responses: {
|
|
413
512
|
/** @description Token returned or pending status */
|
|
414
513
|
200: {
|
|
514
|
+
content: {
|
|
515
|
+
"application/json": components["schemas"]["DeviceAuthPendingResponse"] | components["schemas"]["DeviceAuthTokenResponse"];
|
|
516
|
+
};
|
|
517
|
+
};
|
|
518
|
+
/** @description Invalid device code or expired token */
|
|
519
|
+
400: {
|
|
520
|
+
content: never;
|
|
521
|
+
};
|
|
522
|
+
/** @description Polling too frequently */
|
|
523
|
+
429: {
|
|
524
|
+
content: never;
|
|
525
|
+
};
|
|
526
|
+
/** @description Server error during authentication */
|
|
527
|
+
500: {
|
|
528
|
+
content: never;
|
|
529
|
+
};
|
|
530
|
+
};
|
|
531
|
+
};
|
|
532
|
+
};
|
|
533
|
+
"/v1/auth/refresh": {
|
|
534
|
+
/**
|
|
535
|
+
* Refresh access token
|
|
536
|
+
* @description Refreshes an access token using a refresh token. This endpoint can be used to obtain a new
|
|
537
|
+
* access token when the current one expires.
|
|
538
|
+
*
|
|
539
|
+
* ## Using the CLI
|
|
540
|
+
*
|
|
541
|
+
* The CLI tool handles token refresh automatically:
|
|
542
|
+
* ```
|
|
543
|
+
* # The CLI will refresh tokens as needed
|
|
544
|
+
* npx berget api-keys list
|
|
545
|
+
* ```
|
|
546
|
+
*/
|
|
547
|
+
post: {
|
|
548
|
+
requestBody: {
|
|
549
|
+
content: {
|
|
550
|
+
"application/json": components["schemas"]["RefreshTokenRequest"];
|
|
551
|
+
};
|
|
552
|
+
};
|
|
553
|
+
responses: {
|
|
554
|
+
/** @description New access and refresh tokens */
|
|
555
|
+
200: {
|
|
556
|
+
content: {
|
|
557
|
+
"application/json": components["schemas"]["RefreshTokenResponse"];
|
|
558
|
+
};
|
|
559
|
+
};
|
|
560
|
+
/** @description Invalid or expired refresh token */
|
|
561
|
+
401: {
|
|
415
562
|
content: never;
|
|
416
563
|
};
|
|
417
564
|
};
|
|
@@ -1418,8 +1565,6 @@ export interface components {
|
|
|
1418
1565
|
};
|
|
1419
1566
|
AuthToken: {
|
|
1420
1567
|
accessToken: string;
|
|
1421
|
-
/** @enum {string} */
|
|
1422
|
-
tokenType: "Bearer";
|
|
1423
1568
|
expiresIn: number;
|
|
1424
1569
|
user?: {
|
|
1425
1570
|
id: string;
|
|
@@ -1437,6 +1582,55 @@ export interface components {
|
|
|
1437
1582
|
/** Format: uri */
|
|
1438
1583
|
avatarUrl: string;
|
|
1439
1584
|
};
|
|
1585
|
+
RefreshTokenRequest: {
|
|
1586
|
+
/** @description The refresh token to use */
|
|
1587
|
+
refresh_token: string;
|
|
1588
|
+
/** @description Whether this is a device token */
|
|
1589
|
+
is_device_token?: boolean;
|
|
1590
|
+
};
|
|
1591
|
+
RefreshTokenResponse: {
|
|
1592
|
+
/** @description The new access token */
|
|
1593
|
+
token: string;
|
|
1594
|
+
/** @description The new refresh token */
|
|
1595
|
+
refresh_token: string;
|
|
1596
|
+
/** @description Seconds until the access token expires */
|
|
1597
|
+
expires_in: number;
|
|
1598
|
+
/** @description Seconds until the refresh token expires */
|
|
1599
|
+
refresh_expires_in: number;
|
|
1600
|
+
};
|
|
1601
|
+
DeviceAuthRequest: {
|
|
1602
|
+
/** @description The device code obtained from the device authorization request */
|
|
1603
|
+
device_code: string;
|
|
1604
|
+
};
|
|
1605
|
+
DeviceAuthInitResponse: {
|
|
1606
|
+
/** @description Code used by the device to poll for authentication status */
|
|
1607
|
+
device_code: string;
|
|
1608
|
+
/** @description Code displayed to the user for authentication */
|
|
1609
|
+
user_code: string;
|
|
1610
|
+
/** @description URL where the user should enter the user_code */
|
|
1611
|
+
verification_url: string;
|
|
1612
|
+
/** @description Expiration time in seconds */
|
|
1613
|
+
expires_in: number;
|
|
1614
|
+
/** @description Polling interval in seconds */
|
|
1615
|
+
interval: number;
|
|
1616
|
+
};
|
|
1617
|
+
DeviceAuthPendingResponse: {
|
|
1618
|
+
/**
|
|
1619
|
+
* @description Authentication is still pending
|
|
1620
|
+
* @enum {string}
|
|
1621
|
+
*/
|
|
1622
|
+
status: "pending";
|
|
1623
|
+
};
|
|
1624
|
+
DeviceAuthTokenResponse: {
|
|
1625
|
+
/** @description Access token */
|
|
1626
|
+
token: string;
|
|
1627
|
+
/** @description Refresh token */
|
|
1628
|
+
refresh_token: string;
|
|
1629
|
+
/** @description Access token expiration time in seconds */
|
|
1630
|
+
expires_in: number;
|
|
1631
|
+
/** @description Refresh token expiration time in seconds */
|
|
1632
|
+
refresh_expires_in: number;
|
|
1633
|
+
};
|
|
1440
1634
|
Usage: {
|
|
1441
1635
|
current_period: {
|
|
1442
1636
|
/** Format: date-time */
|
|
@@ -2,11 +2,15 @@ import * as fs from 'fs'
|
|
|
2
2
|
import * as path from 'path'
|
|
3
3
|
import * as os from 'os'
|
|
4
4
|
import chalk from 'chalk'
|
|
5
|
+
import { ApiKeyService } from '../services/api-key-service'
|
|
6
|
+
import readline from 'readline'
|
|
7
|
+
import { logger } from './logger'
|
|
5
8
|
|
|
6
9
|
interface DefaultApiKeyData {
|
|
7
10
|
id: string
|
|
8
11
|
name: string
|
|
9
12
|
prefix: string
|
|
13
|
+
key: string
|
|
10
14
|
}
|
|
11
15
|
|
|
12
16
|
/**
|
|
@@ -44,7 +48,7 @@ export class DefaultApiKeyManager {
|
|
|
44
48
|
this.defaultApiKey = JSON.parse(data)
|
|
45
49
|
}
|
|
46
50
|
} catch (error) {
|
|
47
|
-
|
|
51
|
+
logger.debug('Failed to load default API key configuration')
|
|
48
52
|
this.defaultApiKey = null
|
|
49
53
|
}
|
|
50
54
|
}
|
|
@@ -65,22 +69,29 @@ export class DefaultApiKeyManager {
|
|
|
65
69
|
}
|
|
66
70
|
}
|
|
67
71
|
} catch (error) {
|
|
68
|
-
|
|
72
|
+
logger.debug('Failed to save default API key configuration')
|
|
69
73
|
}
|
|
70
74
|
}
|
|
71
75
|
|
|
72
76
|
/**
|
|
73
77
|
* Set the default API key
|
|
74
78
|
*/
|
|
75
|
-
public setDefaultApiKey(id: string, name: string, prefix: string): void {
|
|
76
|
-
this.defaultApiKey = { id, name, prefix }
|
|
79
|
+
public setDefaultApiKey(id: string, name: string, prefix: string, key: string): void {
|
|
80
|
+
this.defaultApiKey = { id, name, prefix, key }
|
|
77
81
|
this.saveConfig()
|
|
78
82
|
}
|
|
79
83
|
|
|
80
84
|
/**
|
|
81
|
-
* Get the default API key
|
|
85
|
+
* Get the default API key string
|
|
82
86
|
*/
|
|
83
|
-
public getDefaultApiKey():
|
|
87
|
+
public getDefaultApiKey(): string | null {
|
|
88
|
+
return this.defaultApiKey?.key || null
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get the default API key data object
|
|
93
|
+
*/
|
|
94
|
+
public getDefaultApiKeyData(): DefaultApiKeyData | null {
|
|
84
95
|
return this.defaultApiKey
|
|
85
96
|
}
|
|
86
97
|
|
|
@@ -91,4 +102,111 @@ export class DefaultApiKeyManager {
|
|
|
91
102
|
this.defaultApiKey = null
|
|
92
103
|
this.saveConfig()
|
|
93
104
|
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Prompts the user to select a default API key if none is set
|
|
108
|
+
* @returns The selected API key or null if none was selected
|
|
109
|
+
*/
|
|
110
|
+
public async promptForDefaultApiKey(): Promise<string | null> {
|
|
111
|
+
try {
|
|
112
|
+
logger.debug('promptForDefaultApiKey called')
|
|
113
|
+
|
|
114
|
+
// If we already have a default API key, return it
|
|
115
|
+
if (this.defaultApiKey) {
|
|
116
|
+
logger.debug('Using existing default API key')
|
|
117
|
+
return this.defaultApiKey.key
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
logger.debug('No default API key found, getting ApiKeyService')
|
|
121
|
+
|
|
122
|
+
const apiKeyService = ApiKeyService.getInstance()
|
|
123
|
+
|
|
124
|
+
// Get all API keys
|
|
125
|
+
let apiKeys;
|
|
126
|
+
try {
|
|
127
|
+
logger.debug('Calling apiKeyService.list()')
|
|
128
|
+
|
|
129
|
+
apiKeys = await apiKeyService.list()
|
|
130
|
+
|
|
131
|
+
logger.debug(`Got ${apiKeys ? apiKeys.length : 0} API keys`)
|
|
132
|
+
|
|
133
|
+
if (!apiKeys || apiKeys.length === 0) {
|
|
134
|
+
logger.warn('No API keys found. Create one with:')
|
|
135
|
+
logger.info(' berget api-keys create --name "My Key"')
|
|
136
|
+
return null
|
|
137
|
+
}
|
|
138
|
+
} catch (error) {
|
|
139
|
+
// Check if this is an authentication error
|
|
140
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
141
|
+
const isAuthError = errorMessage.includes('Unauthorized') ||
|
|
142
|
+
errorMessage.includes('Authentication failed') ||
|
|
143
|
+
errorMessage.includes('AUTH_FAILED');
|
|
144
|
+
|
|
145
|
+
if (isAuthError) {
|
|
146
|
+
logger.warn('Authentication required. Please run `berget auth login` first.');
|
|
147
|
+
} else {
|
|
148
|
+
logger.error('Error fetching API keys:');
|
|
149
|
+
if (error instanceof Error) {
|
|
150
|
+
logger.error(error.message);
|
|
151
|
+
logger.debug(`API key list error: ${error.message}`);
|
|
152
|
+
logger.debug(`Stack: ${error.stack}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
logger.info('Select an API key to use as default:')
|
|
159
|
+
|
|
160
|
+
// Display available API keys
|
|
161
|
+
apiKeys.forEach((key, index) => {
|
|
162
|
+
logger.log(` ${index + 1}. ${key.name} (${key.prefix}...)`)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
// Create readline interface for user input
|
|
166
|
+
const rl = readline.createInterface({
|
|
167
|
+
input: process.stdin,
|
|
168
|
+
output: process.stdout
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
// Prompt for selection
|
|
172
|
+
const selection = await new Promise<number>((resolve) => {
|
|
173
|
+
rl.question('Enter number (or press Enter to cancel): ', (answer) => {
|
|
174
|
+
rl.close()
|
|
175
|
+
const num = parseInt(answer.trim(), 10)
|
|
176
|
+
if (isNaN(num) || num < 1 || num > apiKeys.length) {
|
|
177
|
+
resolve(-1) // Invalid selection
|
|
178
|
+
} else {
|
|
179
|
+
resolve(num - 1) // Convert to zero-based index
|
|
180
|
+
}
|
|
181
|
+
})
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
if (selection === -1) {
|
|
185
|
+
logger.warn('No API key selected')
|
|
186
|
+
return null
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const selectedKey = apiKeys[selection]
|
|
190
|
+
|
|
191
|
+
// Create a new API key with the selected name
|
|
192
|
+
const newKey = await apiKeyService.create({
|
|
193
|
+
name: `CLI Default (copy of ${selectedKey.name})`,
|
|
194
|
+
description: 'Created automatically by the Berget CLI for default use'
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
// Save the new key as default
|
|
198
|
+
this.setDefaultApiKey(
|
|
199
|
+
newKey.id.toString(),
|
|
200
|
+
newKey.name,
|
|
201
|
+
newKey.key.substring(0, 8), // Use first 8 chars as prefix
|
|
202
|
+
newKey.key
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
logger.success(`✓ Default API key set to: ${newKey.name}`)
|
|
206
|
+
return newKey.key
|
|
207
|
+
} catch (error) {
|
|
208
|
+
logger.error('Failed to set default API key:', error)
|
|
209
|
+
return null
|
|
210
|
+
}
|
|
211
|
+
}
|
|
94
212
|
}
|
|
@@ -30,11 +30,11 @@ export function handleError(message: string, error: any): void {
|
|
|
30
30
|
|
|
31
31
|
// Check for authentication errors
|
|
32
32
|
if (
|
|
33
|
-
(typeof error === 'string' && error.includes('Unauthorized')) ||
|
|
34
|
-
(error && error.message && error.message.includes('Unauthorized')) ||
|
|
35
|
-
(error && error.code && error.code === 401)
|
|
33
|
+
(typeof error === 'string' && (error.includes('Unauthorized') || error.includes('Authentication failed'))) ||
|
|
34
|
+
(error && error.message && (error.message.includes('Unauthorized') || error.message.includes('Authentication failed'))) ||
|
|
35
|
+
(error && error.code && (error.code === 401 || error.code === 'AUTH_FAILED'))
|
|
36
36
|
) {
|
|
37
37
|
console.error(chalk.yellow('\nYou need to be logged in to use this command.'));
|
|
38
|
-
console.error(chalk.yellow('Run `berget login` to authenticate.'));
|
|
38
|
+
console.error(chalk.yellow('Run `berget auth login` to authenticate.'));
|
|
39
39
|
}
|
|
40
40
|
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Log levels in order of increasing verbosity
|
|
5
|
+
*/
|
|
6
|
+
export enum LogLevel {
|
|
7
|
+
NONE = 0,
|
|
8
|
+
ERROR = 1,
|
|
9
|
+
WARN = 2,
|
|
10
|
+
INFO = 3,
|
|
11
|
+
DEBUG = 4
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Logger class for centralized logging with configurable log levels
|
|
16
|
+
*/
|
|
17
|
+
export class Logger {
|
|
18
|
+
private static instance: Logger
|
|
19
|
+
private logLevel: LogLevel = LogLevel.INFO // Default log level
|
|
20
|
+
|
|
21
|
+
private constructor() {
|
|
22
|
+
// Set log level from environment variable or command line argument
|
|
23
|
+
if (process.env.LOG_LEVEL) {
|
|
24
|
+
this.setLogLevelFromString(process.env.LOG_LEVEL)
|
|
25
|
+
} else if (process.argv.includes('--debug')) {
|
|
26
|
+
this.logLevel = LogLevel.DEBUG
|
|
27
|
+
} else if (process.argv.includes('--quiet')) {
|
|
28
|
+
this.logLevel = LogLevel.ERROR
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public static getInstance(): Logger {
|
|
33
|
+
if (!Logger.instance) {
|
|
34
|
+
Logger.instance = new Logger()
|
|
35
|
+
}
|
|
36
|
+
return Logger.instance
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Set the log level from a string
|
|
41
|
+
*/
|
|
42
|
+
private setLogLevelFromString(level: string): void {
|
|
43
|
+
switch (level.toLowerCase()) {
|
|
44
|
+
case 'none':
|
|
45
|
+
this.logLevel = LogLevel.NONE
|
|
46
|
+
break
|
|
47
|
+
case 'error':
|
|
48
|
+
this.logLevel = LogLevel.ERROR
|
|
49
|
+
break
|
|
50
|
+
case 'warn':
|
|
51
|
+
this.logLevel = LogLevel.WARN
|
|
52
|
+
break
|
|
53
|
+
case 'info':
|
|
54
|
+
this.logLevel = LogLevel.INFO
|
|
55
|
+
break
|
|
56
|
+
case 'debug':
|
|
57
|
+
this.logLevel = LogLevel.DEBUG
|
|
58
|
+
break
|
|
59
|
+
default:
|
|
60
|
+
// Invalid log level, keep default
|
|
61
|
+
console.warn(`Invalid log level: ${level}. Using default (INFO).`)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Set the log level
|
|
67
|
+
*/
|
|
68
|
+
public setLogLevel(level: LogLevel): void {
|
|
69
|
+
this.logLevel = level
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get the current log level
|
|
74
|
+
*/
|
|
75
|
+
public getLogLevel(): LogLevel {
|
|
76
|
+
return this.logLevel
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Log a debug message (only shown at DEBUG level)
|
|
81
|
+
*/
|
|
82
|
+
public debug(message: string, ...args: any[]): void {
|
|
83
|
+
if (this.logLevel >= LogLevel.DEBUG) {
|
|
84
|
+
if (args.length > 0) {
|
|
85
|
+
console.log(chalk.yellow(`DEBUG: ${message}`), ...args)
|
|
86
|
+
} else {
|
|
87
|
+
console.log(chalk.yellow(`DEBUG: ${message}`))
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Log an info message (shown at INFO level and above)
|
|
94
|
+
*/
|
|
95
|
+
public info(message: string, ...args: any[]): void {
|
|
96
|
+
if (this.logLevel >= LogLevel.INFO) {
|
|
97
|
+
if (args.length > 0) {
|
|
98
|
+
console.log(chalk.blue(message), ...args)
|
|
99
|
+
} else {
|
|
100
|
+
console.log(chalk.blue(message))
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Log a warning message (shown at WARN level and above)
|
|
107
|
+
*/
|
|
108
|
+
public warn(message: string, ...args: any[]): void {
|
|
109
|
+
if (this.logLevel >= LogLevel.WARN) {
|
|
110
|
+
if (args.length > 0) {
|
|
111
|
+
console.log(chalk.yellow(message), ...args)
|
|
112
|
+
} else {
|
|
113
|
+
console.log(chalk.yellow(message))
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Log an error message (shown at ERROR level and above)
|
|
120
|
+
*/
|
|
121
|
+
public error(message: string, ...args: any[]): void {
|
|
122
|
+
if (this.logLevel >= LogLevel.ERROR) {
|
|
123
|
+
if (args.length > 0) {
|
|
124
|
+
console.error(chalk.red(message), ...args)
|
|
125
|
+
} else {
|
|
126
|
+
console.error(chalk.red(message))
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Log a success message (shown at INFO level and above)
|
|
133
|
+
*/
|
|
134
|
+
public success(message: string, ...args: any[]): void {
|
|
135
|
+
if (this.logLevel >= LogLevel.INFO) {
|
|
136
|
+
if (args.length > 0) {
|
|
137
|
+
console.log(chalk.green(message), ...args)
|
|
138
|
+
} else {
|
|
139
|
+
console.log(chalk.green(message))
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Log a plain message without color (shown at INFO level and above)
|
|
146
|
+
*/
|
|
147
|
+
public log(message: string, ...args: any[]): void {
|
|
148
|
+
if (this.logLevel >= LogLevel.INFO) {
|
|
149
|
+
if (args.length > 0) {
|
|
150
|
+
console.log(message, ...args)
|
|
151
|
+
} else {
|
|
152
|
+
console.log(message)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Export a singleton instance for easy import
|
|
159
|
+
export const logger = Logger.getInstance()
|
|
@@ -2,6 +2,7 @@ import * as fs from 'fs'
|
|
|
2
2
|
import * as path from 'path'
|
|
3
3
|
import * as os from 'os'
|
|
4
4
|
import chalk from 'chalk'
|
|
5
|
+
import { logger } from './logger'
|
|
5
6
|
|
|
6
7
|
interface TokenData {
|
|
7
8
|
access_token: string
|
|
@@ -44,7 +45,7 @@ export class TokenManager {
|
|
|
44
45
|
this.tokenData = JSON.parse(data)
|
|
45
46
|
}
|
|
46
47
|
} catch (error) {
|
|
47
|
-
|
|
48
|
+
logger.error('Failed to load authentication token')
|
|
48
49
|
this.tokenData = null
|
|
49
50
|
}
|
|
50
51
|
}
|
|
@@ -65,7 +66,7 @@ export class TokenManager {
|
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
68
|
} catch (error) {
|
|
68
|
-
|
|
69
|
+
logger.error('Failed to save authentication token')
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
72
|
|
|
@@ -100,14 +101,14 @@ export class TokenManager {
|
|
|
100
101
|
const expirationBuffer = 10 * 60 * 1000 // 10 minutes in milliseconds
|
|
101
102
|
const isExpired = Date.now() + expirationBuffer >= this.tokenData.expires_at;
|
|
102
103
|
|
|
103
|
-
if (isExpired
|
|
104
|
-
|
|
104
|
+
if (isExpired) {
|
|
105
|
+
logger.debug(`Token expired or expiring soon. Current time: ${new Date().toISOString()}, Expiry: ${new Date(this.tokenData.expires_at).toISOString()}`);
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
return isExpired;
|
|
108
109
|
} catch (error) {
|
|
109
110
|
// If there's any error checking expiration, assume token is expired
|
|
110
|
-
|
|
111
|
+
logger.error(`Error checking token expiration: ${error instanceof Error ? error.message : String(error)}`);
|
|
111
112
|
return true;
|
|
112
113
|
}
|
|
113
114
|
}
|
package/tsconfig.json
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
|
40
40
|
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
|
41
41
|
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
|
42
|
-
|
|
42
|
+
"resolveJsonModule": true, /* Enable importing .json files. */
|
|
43
43
|
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
|
44
44
|
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
|
45
45
|
|