berget 1.2.0 → 1.3.1
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/package.json +5 -1
- package/dist/src/client.js +33 -43
- package/dist/src/commands/chat.js +50 -22
- package/dist/src/commands/models.js +2 -2
- package/dist/src/services/chat-service.js +230 -125
- package/dist/src/utils/default-api-key.js +20 -37
- package/dist/src/utils/logger.js +160 -0
- package/dist/src/utils/markdown-renderer.js +73 -0
- package/dist/src/utils/token-manager.js +6 -9
- package/package.json +5 -1
- package/src/client.ts +75 -90
- package/src/commands/chat.ts +64 -29
- package/src/commands/models.ts +4 -4
- package/src/services/chat-service.ts +386 -184
- package/src/utils/default-api-key.ts +20 -37
- package/src/utils/logger.ts +159 -0
- package/src/utils/markdown-renderer.ts +68 -0
- package/src/utils/token-manager.ts +6 -5
|
@@ -4,6 +4,7 @@ import * as os from 'os'
|
|
|
4
4
|
import chalk from 'chalk'
|
|
5
5
|
import { ApiKeyService } from '../services/api-key-service'
|
|
6
6
|
import readline from 'readline'
|
|
7
|
+
import { logger } from './logger'
|
|
7
8
|
|
|
8
9
|
interface DefaultApiKeyData {
|
|
9
10
|
id: string
|
|
@@ -47,9 +48,7 @@ export class DefaultApiKeyManager {
|
|
|
47
48
|
this.defaultApiKey = JSON.parse(data)
|
|
48
49
|
}
|
|
49
50
|
} catch (error) {
|
|
50
|
-
|
|
51
|
-
console.error(chalk.dim('Failed to load default API key configuration'))
|
|
52
|
-
}
|
|
51
|
+
logger.debug('Failed to load default API key configuration')
|
|
53
52
|
this.defaultApiKey = null
|
|
54
53
|
}
|
|
55
54
|
}
|
|
@@ -70,9 +69,7 @@ export class DefaultApiKeyManager {
|
|
|
70
69
|
}
|
|
71
70
|
}
|
|
72
71
|
} catch (error) {
|
|
73
|
-
|
|
74
|
-
console.error(chalk.dim('Failed to save default API key configuration'))
|
|
75
|
-
}
|
|
72
|
+
logger.debug('Failed to save default API key configuration')
|
|
76
73
|
}
|
|
77
74
|
}
|
|
78
75
|
|
|
@@ -112,42 +109,30 @@ export class DefaultApiKeyManager {
|
|
|
112
109
|
*/
|
|
113
110
|
public async promptForDefaultApiKey(): Promise<string | null> {
|
|
114
111
|
try {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (isDebug) {
|
|
118
|
-
console.log(chalk.yellow('DEBUG: promptForDefaultApiKey called'))
|
|
119
|
-
}
|
|
112
|
+
logger.debug('promptForDefaultApiKey called')
|
|
120
113
|
|
|
121
114
|
// If we already have a default API key, return it
|
|
122
115
|
if (this.defaultApiKey) {
|
|
123
|
-
|
|
124
|
-
console.log(chalk.yellow('DEBUG: Using existing default API key'))
|
|
125
|
-
}
|
|
116
|
+
logger.debug('Using existing default API key')
|
|
126
117
|
return this.defaultApiKey.key
|
|
127
118
|
}
|
|
128
119
|
|
|
129
|
-
|
|
130
|
-
console.log(chalk.yellow('DEBUG: No default API key found, getting ApiKeyService'))
|
|
131
|
-
}
|
|
120
|
+
logger.debug('No default API key found, getting ApiKeyService')
|
|
132
121
|
|
|
133
122
|
const apiKeyService = ApiKeyService.getInstance()
|
|
134
123
|
|
|
135
124
|
// Get all API keys
|
|
136
125
|
let apiKeys;
|
|
137
126
|
try {
|
|
138
|
-
|
|
139
|
-
console.log(chalk.yellow('DEBUG: Calling apiKeyService.list()'))
|
|
140
|
-
}
|
|
127
|
+
logger.debug('Calling apiKeyService.list()')
|
|
141
128
|
|
|
142
129
|
apiKeys = await apiKeyService.list()
|
|
143
130
|
|
|
144
|
-
|
|
145
|
-
console.log(chalk.yellow(`DEBUG: Got ${apiKeys ? apiKeys.length : 0} API keys`))
|
|
146
|
-
}
|
|
131
|
+
logger.debug(`Got ${apiKeys ? apiKeys.length : 0} API keys`)
|
|
147
132
|
|
|
148
133
|
if (!apiKeys || apiKeys.length === 0) {
|
|
149
|
-
|
|
150
|
-
|
|
134
|
+
logger.warn('No API keys found. Create one with:')
|
|
135
|
+
logger.info(' berget api-keys create --name "My Key"')
|
|
151
136
|
return null
|
|
152
137
|
}
|
|
153
138
|
} catch (error) {
|
|
@@ -158,25 +143,23 @@ export class DefaultApiKeyManager {
|
|
|
158
143
|
errorMessage.includes('AUTH_FAILED');
|
|
159
144
|
|
|
160
145
|
if (isAuthError) {
|
|
161
|
-
|
|
146
|
+
logger.warn('Authentication required. Please run `berget auth login` first.');
|
|
162
147
|
} else {
|
|
163
|
-
|
|
148
|
+
logger.error('Error fetching API keys:');
|
|
164
149
|
if (error instanceof Error) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
console.log(chalk.yellow(`DEBUG: Stack: ${error.stack}`));
|
|
169
|
-
}
|
|
150
|
+
logger.error(error.message);
|
|
151
|
+
logger.debug(`API key list error: ${error.message}`);
|
|
152
|
+
logger.debug(`Stack: ${error.stack}`);
|
|
170
153
|
}
|
|
171
154
|
}
|
|
172
155
|
return null;
|
|
173
156
|
}
|
|
174
157
|
|
|
175
|
-
|
|
158
|
+
logger.info('Select an API key to use as default:')
|
|
176
159
|
|
|
177
160
|
// Display available API keys
|
|
178
161
|
apiKeys.forEach((key, index) => {
|
|
179
|
-
|
|
162
|
+
logger.log(` ${index + 1}. ${key.name} (${key.prefix}...)`)
|
|
180
163
|
})
|
|
181
164
|
|
|
182
165
|
// Create readline interface for user input
|
|
@@ -199,7 +182,7 @@ export class DefaultApiKeyManager {
|
|
|
199
182
|
})
|
|
200
183
|
|
|
201
184
|
if (selection === -1) {
|
|
202
|
-
|
|
185
|
+
logger.warn('No API key selected')
|
|
203
186
|
return null
|
|
204
187
|
}
|
|
205
188
|
|
|
@@ -219,10 +202,10 @@ export class DefaultApiKeyManager {
|
|
|
219
202
|
newKey.key
|
|
220
203
|
)
|
|
221
204
|
|
|
222
|
-
|
|
205
|
+
logger.success(`✓ Default API key set to: ${newKey.name}`)
|
|
223
206
|
return newKey.key
|
|
224
207
|
} catch (error) {
|
|
225
|
-
|
|
208
|
+
logger.error('Failed to set default API key:', error)
|
|
226
209
|
return null
|
|
227
210
|
}
|
|
228
211
|
}
|
|
@@ -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()
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { marked } from 'marked'
|
|
2
|
+
import TerminalRenderer from 'marked-terminal'
|
|
3
|
+
import chalk from 'chalk'
|
|
4
|
+
|
|
5
|
+
// Configure marked to use the terminal renderer
|
|
6
|
+
marked.setOptions({
|
|
7
|
+
renderer: new TerminalRenderer({
|
|
8
|
+
// Customize the rendering options
|
|
9
|
+
code: chalk.cyan,
|
|
10
|
+
blockquote: chalk.gray.italic,
|
|
11
|
+
table: chalk.white,
|
|
12
|
+
listitem: chalk.yellow,
|
|
13
|
+
strong: chalk.bold,
|
|
14
|
+
em: chalk.italic,
|
|
15
|
+
heading: chalk.bold.blueBright,
|
|
16
|
+
hr: chalk.gray,
|
|
17
|
+
link: chalk.blue.underline,
|
|
18
|
+
// Adjust the width to fit the terminal
|
|
19
|
+
width: process.stdout.columns || 80,
|
|
20
|
+
// Customize code block rendering
|
|
21
|
+
codespan: chalk.cyan
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Render markdown text to terminal-friendly formatted text
|
|
27
|
+
* @param markdown The markdown text to render
|
|
28
|
+
* @returns Formatted text for terminal display
|
|
29
|
+
*/
|
|
30
|
+
export function renderMarkdown(markdown: string): string {
|
|
31
|
+
if (!markdown) return ''
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
// Convert markdown to terminal-friendly text
|
|
35
|
+
return marked(markdown)
|
|
36
|
+
} catch (error) {
|
|
37
|
+
// If rendering fails, return the original text
|
|
38
|
+
console.error(`Error rendering markdown: ${error}`)
|
|
39
|
+
return markdown
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Check if a string contains markdown formatting
|
|
45
|
+
* @param text The text to check
|
|
46
|
+
* @returns True if the text contains markdown formatting
|
|
47
|
+
*/
|
|
48
|
+
export function containsMarkdown(text: string): boolean {
|
|
49
|
+
if (!text) return false
|
|
50
|
+
|
|
51
|
+
// Check for common markdown patterns
|
|
52
|
+
const markdownPatterns = [
|
|
53
|
+
/^#+\s+/m, // Headers
|
|
54
|
+
/\*\*.*?\*\*/, // Bold
|
|
55
|
+
/\*.*?\*/, // Italic
|
|
56
|
+
/`.*?`/, // Inline code
|
|
57
|
+
/```[\s\S]*?```/, // Code blocks
|
|
58
|
+
/\[.*?\]\(.*?\)/, // Links
|
|
59
|
+
/^\s*[-*+]\s+/m, // Lists
|
|
60
|
+
/^\s*\d+\.\s+/m, // Numbered lists
|
|
61
|
+
/^\s*>\s+/m, // Blockquotes
|
|
62
|
+
/\|.*\|.*\|/, // Tables
|
|
63
|
+
/^---+$/m, // Horizontal rules
|
|
64
|
+
/^===+$/m // Alternative headers
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
return markdownPatterns.some(pattern => pattern.test(text))
|
|
68
|
+
}
|
|
@@ -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
|
}
|