berget 1.3.1 → 1.4.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/.github/workflows/publish.yml +56 -0
- package/.github/workflows/test.yml +38 -0
- package/README.md +177 -38
- package/dist/package.json +8 -2
- package/dist/src/commands/chat.js +166 -17
- package/dist/src/services/chat-service.js +10 -10
- package/dist/tests/commands/chat.test.js +107 -0
- package/dist/vitest.config.js +9 -0
- package/examples/README.md +95 -0
- package/examples/ai-review.sh +30 -0
- package/examples/install-global-security-hook.sh +170 -0
- package/examples/security-check.sh +102 -0
- package/examples/smart-commit.sh +26 -0
- package/package.json +8 -2
- package/src/commands/chat.ts +169 -17
- package/src/services/chat-service.ts +13 -23
- package/tests/commands/chat.test.ts +117 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
name: Publish to NPM
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
release:
|
|
8
|
+
types: [published]
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
test:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- name: Checkout code
|
|
15
|
+
uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Setup Node.js
|
|
18
|
+
uses: actions/setup-node@v4
|
|
19
|
+
with:
|
|
20
|
+
node-version: '18'
|
|
21
|
+
cache: 'npm'
|
|
22
|
+
|
|
23
|
+
- name: Install dependencies
|
|
24
|
+
run: npm ci
|
|
25
|
+
|
|
26
|
+
- name: Run tests
|
|
27
|
+
run: npm run test:run
|
|
28
|
+
|
|
29
|
+
- name: Build project
|
|
30
|
+
run: npm run build
|
|
31
|
+
|
|
32
|
+
publish:
|
|
33
|
+
needs: test
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
if: github.ref == 'refs/heads/main' || github.event_name == 'release'
|
|
36
|
+
steps:
|
|
37
|
+
- name: Checkout code
|
|
38
|
+
uses: actions/checkout@v4
|
|
39
|
+
|
|
40
|
+
- name: Setup Node.js
|
|
41
|
+
uses: actions/setup-node@v4
|
|
42
|
+
with:
|
|
43
|
+
node-version: '18'
|
|
44
|
+
registry-url: 'https://registry.npmjs.org'
|
|
45
|
+
cache: 'npm'
|
|
46
|
+
|
|
47
|
+
- name: Install dependencies
|
|
48
|
+
run: npm ci
|
|
49
|
+
|
|
50
|
+
- name: Build project
|
|
51
|
+
run: npm run build
|
|
52
|
+
|
|
53
|
+
- name: Publish to NPM
|
|
54
|
+
run: npm publish
|
|
55
|
+
env:
|
|
56
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: Test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
push:
|
|
8
|
+
branches:
|
|
9
|
+
- main
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
test:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
strategy:
|
|
15
|
+
matrix:
|
|
16
|
+
node-version: [18, 20]
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- name: Checkout code
|
|
20
|
+
uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
- name: Setup Node.js ${{ matrix.node-version }}
|
|
23
|
+
uses: actions/setup-node@v4
|
|
24
|
+
with:
|
|
25
|
+
node-version: ${{ matrix.node-version }}
|
|
26
|
+
cache: 'npm'
|
|
27
|
+
|
|
28
|
+
- name: Install dependencies
|
|
29
|
+
run: npm ci
|
|
30
|
+
|
|
31
|
+
- name: Run tests
|
|
32
|
+
run: npm run test:run
|
|
33
|
+
|
|
34
|
+
- name: Build project
|
|
35
|
+
run: npm run build
|
|
36
|
+
|
|
37
|
+
- name: Check TypeScript
|
|
38
|
+
run: npx tsc --noEmit
|
package/README.md
CHANGED
|
@@ -1,33 +1,193 @@
|
|
|
1
1
|
# Berget CLI
|
|
2
2
|
|
|
3
|
-
A command-line
|
|
3
|
+
A command-line tool for interacting with Berget AI's infrastructure and AI models.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npm install -g berget
|
|
9
|
+
# or use directly with npx
|
|
10
|
+
npx berget --help
|
|
9
11
|
```
|
|
10
12
|
|
|
11
13
|
## Authentication
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
Before you can use the CLI, you need to authenticate:
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
```bash
|
|
18
|
+
# Login with OAuth
|
|
19
|
+
npx berget auth login
|
|
20
|
+
|
|
21
|
+
# Create an API key
|
|
22
|
+
npx berget api-keys create --name "My CLI Key"
|
|
23
|
+
|
|
24
|
+
# Or use environment variable
|
|
25
|
+
export BERGET_API_KEY=sk_ber_your_api_key_here
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Chat Command
|
|
29
|
+
|
|
30
|
+
### Basic Usage
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Interactive chat session
|
|
34
|
+
npx berget chat run
|
|
35
|
+
|
|
36
|
+
# Use specific model
|
|
37
|
+
npx berget chat run openai/gpt-oss
|
|
38
|
+
|
|
39
|
+
# Send direct message
|
|
40
|
+
npx berget chat run openai/gpt-oss "Explain what Docker is"
|
|
41
|
+
|
|
42
|
+
# Use pipe for input
|
|
43
|
+
echo "What is Kubernetes?" | npx berget chat run openai/gpt-oss
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Practical Use Cases
|
|
47
|
+
|
|
48
|
+
#### 1. Git Commit Messages
|
|
16
49
|
|
|
17
50
|
```bash
|
|
18
|
-
|
|
51
|
+
# Generate commit message from git diff
|
|
52
|
+
git diff | npx berget chat run openai/gpt-oss "Create a conventional commit message for this diff. Reply with only the message:"
|
|
53
|
+
|
|
54
|
+
# Use as alias
|
|
55
|
+
alias gitcommit='git diff | npx berget chat run openai/gpt-oss "Generate a conventional commit message for this diff. Reply with only the commit message, nothing else:"'
|
|
19
56
|
```
|
|
20
57
|
|
|
21
|
-
|
|
58
|
+
#### 2. Code Review and Explanations
|
|
22
59
|
|
|
23
|
-
|
|
60
|
+
```bash
|
|
61
|
+
# Explain code
|
|
62
|
+
cat src/main.js | npx berget chat run openai/gpt-oss "Explain what this code does:"
|
|
24
63
|
|
|
25
|
-
|
|
64
|
+
# Find bugs
|
|
65
|
+
cat problematic-file.py | npx berget chat run openai/gpt-oss "Analyze this code and find potential bugs:"
|
|
26
66
|
|
|
27
|
-
|
|
28
|
-
|
|
67
|
+
# Improvement suggestions
|
|
68
|
+
git diff | npx berget chat run openai/gpt-oss "Give suggestions for improvements to this code change:"
|
|
69
|
+
```
|
|
29
70
|
|
|
30
|
-
|
|
71
|
+
#### 3. Documentation
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# Generate README
|
|
75
|
+
ls -la | npx berget chat run openai/gpt-oss "Create a README.md for this project based on the file structure:"
|
|
76
|
+
|
|
77
|
+
# Comment code
|
|
78
|
+
cat uncommented-code.js | npx berget chat run openai/gpt-oss "Add JSDoc comments to this code:"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
#### 4. System Administration
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Analyze logs
|
|
85
|
+
tail -n 100 /var/log/nginx/error.log | npx berget chat run openai/gpt-oss "Analyze these error logs and suggest solutions:"
|
|
86
|
+
|
|
87
|
+
# Explain commands
|
|
88
|
+
npx berget chat run openai/gpt-oss "Explain what this bash command does: find . -name '*.js' -exec grep -l 'TODO' {} \;"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Useful Bash/Zsh Aliases
|
|
92
|
+
|
|
93
|
+
Add these to your `~/.bashrc`, `~/.zshrc` or similar:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Git-related aliases
|
|
97
|
+
alias gai='git diff | npx berget chat run openai/gpt-oss "Generate a conventional commit message for this diff. Reply with only the commit message, nothing else:"'
|
|
98
|
+
alias gexplain='git log --oneline -10 | npx berget chat run openai/gpt-oss "Explain what these commits do:"'
|
|
99
|
+
alias gsec='~/bin/security-check'
|
|
100
|
+
|
|
101
|
+
# Code-related aliases
|
|
102
|
+
alias explain='npx berget chat run openai/gpt-oss "Explain this code:"'
|
|
103
|
+
alias review='npx berget chat run openai/gpt-oss "Review this code and give improvement suggestions:"'
|
|
104
|
+
alias debug='npx berget chat run openai/gpt-oss "Find and explain potential bugs in this code:"'
|
|
105
|
+
|
|
106
|
+
# Documentation aliases
|
|
107
|
+
alias docgen='npx berget chat run openai/gpt-oss "Generate documentation for this code:"'
|
|
108
|
+
alias readme='ls -la | npx berget chat run openai/gpt-oss "Create a README.md for this project:"'
|
|
109
|
+
|
|
110
|
+
# System aliases
|
|
111
|
+
alias loganalyze='npx berget chat run openai/gpt-oss "Analyze these logs and suggest solutions:"'
|
|
112
|
+
alias cmdexplain='npx berget chat run openai/gpt-oss "Explain this command:"'
|
|
113
|
+
|
|
114
|
+
# Quick AI assistant
|
|
115
|
+
alias ai='npx berget chat run openai/gpt-oss'
|
|
116
|
+
alias ask='npx berget chat run openai/gpt-oss'
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Advanced Examples
|
|
120
|
+
|
|
121
|
+
See the `examples/` folder for complete scripts:
|
|
122
|
+
|
|
123
|
+
- **smart-commit.sh** - Automatic generation of conventional commit messages
|
|
124
|
+
- **ai-review.sh** - AI-driven code review
|
|
125
|
+
- **security-check.sh** - Security review of commits
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
# Copy example scripts
|
|
129
|
+
cp examples/*.sh ~/bin/
|
|
130
|
+
chmod +x ~/bin/*.sh
|
|
131
|
+
|
|
132
|
+
# Use them
|
|
133
|
+
~/bin/smart-commit.sh
|
|
134
|
+
~/bin/ai-review.sh src/main.js
|
|
135
|
+
~/bin/security-check.sh
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Environment Variables
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
# API key (recommended)
|
|
142
|
+
export BERGET_API_KEY=sk_ber_your_api_key_here
|
|
143
|
+
|
|
144
|
+
# Debug mode
|
|
145
|
+
export LOG_LEVEL=debug
|
|
146
|
+
|
|
147
|
+
# Custom API base URL (if using your own instance)
|
|
148
|
+
export API_BASE_URL=https://your-custom-api.example.com
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Tips and Tricks
|
|
152
|
+
|
|
153
|
+
1. **Use pipes**: Combine with other Unix tools for powerful workflows
|
|
154
|
+
2. **Short prompts**: Be specific but concise in your prompts for best results
|
|
155
|
+
3. **Streaming**: Streaming is enabled by default for faster responses
|
|
156
|
+
4. **Model selection**: Experiment with different models for different tasks
|
|
157
|
+
5. **Aliases**: Create aliases for common use cases to save time
|
|
158
|
+
|
|
159
|
+
## Command Reference
|
|
160
|
+
|
|
161
|
+
- `auth login` - Login to Berget
|
|
162
|
+
- `auth logout` - Logout from Berget
|
|
163
|
+
- `auth whoami` - Show current user information
|
|
164
|
+
- `api-keys list` - List API keys
|
|
165
|
+
- `api-keys create` - Create a new API key
|
|
166
|
+
- `models list` - List available AI models
|
|
167
|
+
- `chat run` - Start a chat session with an AI model
|
|
168
|
+
- `chat list` - List available chat models
|
|
169
|
+
|
|
170
|
+
For a complete list of commands, run:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
npx berget --help
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Troubleshooting
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
# Enable debug mode
|
|
180
|
+
npx berget --debug chat run openai/gpt-oss "test"
|
|
181
|
+
|
|
182
|
+
# Check authentication
|
|
183
|
+
npx berget auth whoami
|
|
184
|
+
|
|
185
|
+
# List available models
|
|
186
|
+
npx berget chat list
|
|
187
|
+
|
|
188
|
+
# Check API key status
|
|
189
|
+
npx berget api-keys list
|
|
190
|
+
```
|
|
31
191
|
|
|
32
192
|
## Development
|
|
33
193
|
|
|
@@ -36,12 +196,12 @@ The refresh mechanism uses the stored refresh token to obtain a new access token
|
|
|
36
196
|
Clone the repository and install dependencies:
|
|
37
197
|
|
|
38
198
|
```bash
|
|
39
|
-
git clone https://github.com/berget/cli.git
|
|
199
|
+
git clone https://github.com/berget-ai/cli.git
|
|
40
200
|
cd cli
|
|
41
201
|
npm install
|
|
42
202
|
```
|
|
43
203
|
|
|
44
|
-
###
|
|
204
|
+
### Test Locally
|
|
45
205
|
|
|
46
206
|
Use the `start` script to test the CLI locally with the `--local` flag:
|
|
47
207
|
|
|
@@ -62,31 +222,10 @@ npm start -- auth whoami
|
|
|
62
222
|
npm start -- auth whoami --debug
|
|
63
223
|
```
|
|
64
224
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
### Testing Token Refresh
|
|
68
|
-
|
|
69
|
-
To test the token refresh mechanism:
|
|
225
|
+
## Contributing
|
|
70
226
|
|
|
71
|
-
|
|
72
|
-
2. Make a request that requires authentication, like `npm start -- auth whoami`
|
|
73
|
-
3. To force a token refresh, you can:
|
|
74
|
-
- Wait until the token is close to expiration
|
|
75
|
-
- Manually edit `~/.berget/auth.json` and set `expires_at` to a past timestamp
|
|
76
|
-
- Use the `--debug` flag to see the token refresh process in action
|
|
227
|
+
Berget CLI is open source. Contributions are welcome!
|
|
77
228
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
-
|
|
81
|
-
- `auth logout` - Log out from Berget
|
|
82
|
-
- `auth whoami` - Show current user information
|
|
83
|
-
- `api-keys list` - List API keys
|
|
84
|
-
- `api-keys create` - Create a new API key
|
|
85
|
-
- `models list` - List available AI models
|
|
86
|
-
- `chat run` - Start a chat session with an AI model
|
|
87
|
-
|
|
88
|
-
For a complete list of commands, run:
|
|
89
|
-
|
|
90
|
-
```bash
|
|
91
|
-
berget --help
|
|
92
|
-
```
|
|
229
|
+
- GitHub: [berget-ai/cli](https://github.com/berget-ai/cli)
|
|
230
|
+
- Issues: [Report bugs](https://github.com/berget-ai/cli/issues)
|
|
231
|
+
- Documentation: [docs.berget.ai](https://docs.berget.ai)
|
package/dist/package.json
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "berget",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"berget": "dist/index.js"
|
|
7
7
|
},
|
|
8
8
|
"private": false,
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public"
|
|
11
|
+
},
|
|
9
12
|
"scripts": {
|
|
10
13
|
"start": "node --import tsx ./index.ts --local",
|
|
11
14
|
"login": "node --import tsx ./index.ts --local auth login",
|
|
12
15
|
"logout": "node --import tsx ./index.ts --local auth logout",
|
|
13
16
|
"whoami": "node --import tsx ./index.ts --local auth whoami",
|
|
14
17
|
"build": "tsc",
|
|
18
|
+
"test": "vitest",
|
|
19
|
+
"test:run": "vitest run",
|
|
15
20
|
"prepublishOnly": "npm run build",
|
|
16
21
|
"generate-types": "openapi-typescript https://api.berget.ai/openapi.json -o src/types/api.d.ts"
|
|
17
22
|
},
|
|
@@ -23,7 +28,8 @@
|
|
|
23
28
|
"@types/marked-terminal": "^6.1.1",
|
|
24
29
|
"@types/node": "^20.11.20",
|
|
25
30
|
"tsx": "^4.19.3",
|
|
26
|
-
"typescript": "^5.3.3"
|
|
31
|
+
"typescript": "^5.3.3",
|
|
32
|
+
"vitest": "^1.0.0"
|
|
27
33
|
},
|
|
28
34
|
"dependencies": {
|
|
29
35
|
"chalk": "^4.1.2",
|
|
@@ -8,6 +8,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
+
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
12
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
13
|
+
var m = o[Symbol.asyncIterator], i;
|
|
14
|
+
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
15
|
+
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
16
|
+
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
17
|
+
};
|
|
11
18
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
19
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
20
|
};
|
|
@@ -49,14 +56,16 @@ function registerChatCommands(program) {
|
|
|
49
56
|
chat
|
|
50
57
|
.command(command_structure_1.SUBCOMMANDS.CHAT.RUN)
|
|
51
58
|
.description('Run a chat session with a specified model')
|
|
52
|
-
.argument('[model]', 'Model to use (default:
|
|
59
|
+
.argument('[model]', 'Model to use (default: openai/gpt-oss)')
|
|
60
|
+
.argument('[message]', 'Message to send directly (skips interactive mode)')
|
|
53
61
|
.option('-s, --system <message>', 'System message')
|
|
54
62
|
.option('-t, --temperature <temp>', 'Temperature (0-1)', parseFloat)
|
|
55
63
|
.option('-m, --max-tokens <tokens>', 'Maximum tokens to generate', parseInt)
|
|
56
64
|
.option('-k, --api-key <key>', 'API key to use for this chat session')
|
|
57
65
|
.option('--api-key-id <id>', 'ID of the API key to use from your saved keys')
|
|
58
|
-
.option('--stream', '
|
|
59
|
-
.action((options) => __awaiter(this, void 0, void 0, function* () {
|
|
66
|
+
.option('--no-stream', 'Disable streaming (streaming is enabled by default)')
|
|
67
|
+
.action((model, message, options) => __awaiter(this, void 0, void 0, function* () {
|
|
68
|
+
var _a, e_1, _b, _c;
|
|
60
69
|
try {
|
|
61
70
|
const chatService = chat_service_1.ChatService.getInstance();
|
|
62
71
|
// Check if we have an API key or need to get one
|
|
@@ -168,11 +177,6 @@ function registerChatCommands(program) {
|
|
|
168
177
|
return;
|
|
169
178
|
}
|
|
170
179
|
}
|
|
171
|
-
// Set up readline interface for user input
|
|
172
|
-
const rl = readline_1.default.createInterface({
|
|
173
|
-
input: process.stdin,
|
|
174
|
-
output: process.stdout,
|
|
175
|
-
});
|
|
176
180
|
// Prepare messages array
|
|
177
181
|
const messages = [];
|
|
178
182
|
// Add system message if provided
|
|
@@ -182,12 +186,134 @@ function registerChatCommands(program) {
|
|
|
182
186
|
content: options.system,
|
|
183
187
|
});
|
|
184
188
|
}
|
|
189
|
+
// Check if input is being piped in
|
|
190
|
+
let inputMessage = message;
|
|
191
|
+
let stdinContent = '';
|
|
192
|
+
if (!process.stdin.isTTY) {
|
|
193
|
+
// Read from stdin (piped input)
|
|
194
|
+
const chunks = [];
|
|
195
|
+
try {
|
|
196
|
+
for (var _d = true, _e = __asyncValues(process.stdin), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
|
|
197
|
+
_c = _f.value;
|
|
198
|
+
_d = false;
|
|
199
|
+
const chunk = _c;
|
|
200
|
+
chunks.push(chunk);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
204
|
+
finally {
|
|
205
|
+
try {
|
|
206
|
+
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
|
207
|
+
}
|
|
208
|
+
finally { if (e_1) throw e_1.error; }
|
|
209
|
+
}
|
|
210
|
+
stdinContent = Buffer.concat(chunks).toString('utf8').trim();
|
|
211
|
+
}
|
|
212
|
+
// Combine stdin content with message if both exist
|
|
213
|
+
if (stdinContent && message) {
|
|
214
|
+
inputMessage = `${stdinContent}\n\n${message}`;
|
|
215
|
+
}
|
|
216
|
+
else if (stdinContent && !message) {
|
|
217
|
+
inputMessage = stdinContent;
|
|
218
|
+
}
|
|
219
|
+
// If a message is provided (either as argument, from stdin, or both), send it directly and exit
|
|
220
|
+
if (inputMessage) {
|
|
221
|
+
// Add user message
|
|
222
|
+
messages.push({
|
|
223
|
+
role: 'user',
|
|
224
|
+
content: inputMessage,
|
|
225
|
+
});
|
|
226
|
+
try {
|
|
227
|
+
// Call the API
|
|
228
|
+
const completionOptions = {
|
|
229
|
+
model: model || 'openai/gpt-oss',
|
|
230
|
+
messages: messages,
|
|
231
|
+
temperature: options.temperature !== undefined ? options.temperature : 0.7,
|
|
232
|
+
max_tokens: options.maxTokens || 4096,
|
|
233
|
+
stream: options.stream !== false
|
|
234
|
+
};
|
|
235
|
+
// Only add apiKey if it actually exists
|
|
236
|
+
if (apiKey) {
|
|
237
|
+
completionOptions.apiKey = apiKey;
|
|
238
|
+
}
|
|
239
|
+
// Add streaming support (now default)
|
|
240
|
+
if (completionOptions.stream) {
|
|
241
|
+
let assistantResponse = '';
|
|
242
|
+
// Stream the response in real-time
|
|
243
|
+
completionOptions.onChunk = (chunk) => {
|
|
244
|
+
if (chunk.choices && chunk.choices[0] && chunk.choices[0].delta && chunk.choices[0].delta.content) {
|
|
245
|
+
const content = chunk.choices[0].delta.content;
|
|
246
|
+
try {
|
|
247
|
+
process.stdout.write(content);
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
// Handle EPIPE errors gracefully (when pipe is closed)
|
|
251
|
+
if (error.code === 'EPIPE') {
|
|
252
|
+
// Stop streaming if the pipe is closed
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
assistantResponse += content;
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
try {
|
|
261
|
+
yield chatService.createCompletion(completionOptions);
|
|
262
|
+
}
|
|
263
|
+
catch (streamError) {
|
|
264
|
+
console.error(chalk_1.default.red('\nStreaming error:'), streamError);
|
|
265
|
+
// Fallback to non-streaming if streaming fails
|
|
266
|
+
console.log(chalk_1.default.yellow('Falling back to non-streaming mode...'));
|
|
267
|
+
completionOptions.stream = false;
|
|
268
|
+
delete completionOptions.onChunk;
|
|
269
|
+
const response = yield chatService.createCompletion(completionOptions);
|
|
270
|
+
if (response && response.choices && response.choices[0] && response.choices[0].message) {
|
|
271
|
+
assistantResponse = response.choices[0].message.content;
|
|
272
|
+
console.log(assistantResponse);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
console.log(); // Add newline at the end
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
const response = yield chatService.createCompletion(completionOptions);
|
|
279
|
+
// Check if response has the expected structure
|
|
280
|
+
if (!response ||
|
|
281
|
+
!response.choices ||
|
|
282
|
+
!response.choices[0] ||
|
|
283
|
+
!response.choices[0].message) {
|
|
284
|
+
console.error(chalk_1.default.red('Error: Unexpected response format from API'));
|
|
285
|
+
console.error(chalk_1.default.red('Response:', JSON.stringify(response, null, 2)));
|
|
286
|
+
throw new Error('Unexpected response format from API');
|
|
287
|
+
}
|
|
288
|
+
// Get assistant's response
|
|
289
|
+
const assistantMessage = response.choices[0].message.content;
|
|
290
|
+
// Display the response
|
|
291
|
+
if ((0, markdown_renderer_1.containsMarkdown)(assistantMessage)) {
|
|
292
|
+
console.log((0, markdown_renderer_1.renderMarkdown)(assistantMessage));
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
console.log(assistantMessage);
|
|
296
|
+
}
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
console.error(chalk_1.default.red('Error: Failed to get response'));
|
|
301
|
+
if (error instanceof Error) {
|
|
302
|
+
console.error(chalk_1.default.red(error.message));
|
|
303
|
+
}
|
|
304
|
+
process.exit(1);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
// Set up readline interface for user input (only for interactive mode)
|
|
308
|
+
const rl = readline_1.default.createInterface({
|
|
309
|
+
input: process.stdin,
|
|
310
|
+
output: process.stdout,
|
|
311
|
+
});
|
|
185
312
|
console.log(chalk_1.default.cyan('Chat with Berget AI (type "exit" to quit)'));
|
|
186
313
|
console.log(chalk_1.default.cyan('----------------------------------------'));
|
|
187
314
|
// Start the conversation loop
|
|
188
315
|
const askQuestion = () => {
|
|
189
316
|
rl.question(chalk_1.default.green('You: '), (input) => __awaiter(this, void 0, void 0, function* () {
|
|
190
|
-
var _a;
|
|
191
317
|
// Check if user wants to exit
|
|
192
318
|
if (input.toLowerCase() === 'exit') {
|
|
193
319
|
console.log(chalk_1.default.cyan('Goodbye!'));
|
|
@@ -202,30 +328,53 @@ function registerChatCommands(program) {
|
|
|
202
328
|
try {
|
|
203
329
|
// Call the API
|
|
204
330
|
const completionOptions = {
|
|
205
|
-
model:
|
|
331
|
+
model: model || 'openai/gpt-oss',
|
|
206
332
|
messages: messages,
|
|
207
333
|
temperature: options.temperature !== undefined ? options.temperature : 0.7,
|
|
208
334
|
max_tokens: options.maxTokens || 4096,
|
|
209
|
-
stream: options.stream
|
|
335
|
+
stream: options.stream !== false
|
|
210
336
|
};
|
|
211
337
|
// Only add apiKey if it actually exists
|
|
212
338
|
if (apiKey) {
|
|
213
339
|
completionOptions.apiKey = apiKey;
|
|
214
340
|
}
|
|
215
|
-
// Add streaming support
|
|
216
|
-
if (
|
|
341
|
+
// Add streaming support (now default)
|
|
342
|
+
if (completionOptions.stream) {
|
|
217
343
|
let assistantResponse = '';
|
|
218
344
|
console.log(chalk_1.default.blue('Assistant: '));
|
|
219
|
-
//
|
|
220
|
-
// since markdown needs the complete text to render properly
|
|
345
|
+
// Stream the response in real-time
|
|
221
346
|
completionOptions.onChunk = (chunk) => {
|
|
222
347
|
if (chunk.choices && chunk.choices[0] && chunk.choices[0].delta && chunk.choices[0].delta.content) {
|
|
223
348
|
const content = chunk.choices[0].delta.content;
|
|
224
|
-
|
|
349
|
+
try {
|
|
350
|
+
process.stdout.write(content);
|
|
351
|
+
}
|
|
352
|
+
catch (error) {
|
|
353
|
+
// Handle EPIPE errors gracefully (when pipe is closed)
|
|
354
|
+
if (error.code === 'EPIPE') {
|
|
355
|
+
// Stop streaming if the pipe is closed
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
throw error;
|
|
359
|
+
}
|
|
225
360
|
assistantResponse += content;
|
|
226
361
|
}
|
|
227
362
|
};
|
|
228
|
-
|
|
363
|
+
try {
|
|
364
|
+
yield chatService.createCompletion(completionOptions);
|
|
365
|
+
}
|
|
366
|
+
catch (streamError) {
|
|
367
|
+
console.error(chalk_1.default.red('\nStreaming error:'), streamError);
|
|
368
|
+
// Fallback to non-streaming if streaming fails
|
|
369
|
+
console.log(chalk_1.default.yellow('Falling back to non-streaming mode...'));
|
|
370
|
+
completionOptions.stream = false;
|
|
371
|
+
delete completionOptions.onChunk;
|
|
372
|
+
const response = yield chatService.createCompletion(completionOptions);
|
|
373
|
+
if (response && response.choices && response.choices[0] && response.choices[0].message) {
|
|
374
|
+
assistantResponse = response.choices[0].message.content;
|
|
375
|
+
console.log(assistantResponse);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
229
378
|
console.log('\n');
|
|
230
379
|
// Add assistant response to messages
|
|
231
380
|
messages.push({
|
|
@@ -256,26 +256,26 @@ class ChatService {
|
|
|
256
256
|
*/
|
|
257
257
|
handleStreamingResponse(options, headers) {
|
|
258
258
|
return __awaiter(this, void 0, void 0, function* () {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
const url = new URL(`${
|
|
262
|
-
// Debug the headers and options
|
|
263
|
-
logger_1.logger.debug('Streaming headers:');
|
|
264
|
-
logger_1.logger.debug(JSON.stringify(headers, null, 2));
|
|
265
|
-
logger_1.logger.debug('Streaming options:');
|
|
266
|
-
logger_1.logger.debug(JSON.stringify(Object.assign(Object.assign({}, options), { onChunk: options.onChunk ? 'function present' : 'no function' }), null, 2));
|
|
259
|
+
// Use the same base URL as the client
|
|
260
|
+
const baseUrl = process.env.API_BASE_URL || 'https://api.berget.ai';
|
|
261
|
+
const url = new URL(`${baseUrl}/v1/chat/completions`);
|
|
267
262
|
try {
|
|
263
|
+
logger_1.logger.debug(`Making streaming request to: ${url.toString()}`);
|
|
264
|
+
logger_1.logger.debug(`Headers:`, JSON.stringify(headers, null, 2));
|
|
265
|
+
logger_1.logger.debug(`Body:`, JSON.stringify(options, null, 2));
|
|
268
266
|
// Make fetch request directly to handle streaming
|
|
269
267
|
const response = yield fetch(url.toString(), {
|
|
270
268
|
method: 'POST',
|
|
271
269
|
headers: Object.assign({ 'Content-Type': 'application/json', Accept: 'text/event-stream' }, headers),
|
|
272
270
|
body: JSON.stringify(options),
|
|
273
271
|
});
|
|
272
|
+
logger_1.logger.debug(`Response status: ${response.status}`);
|
|
273
|
+
logger_1.logger.debug(`Response headers:`, JSON.stringify(Object.fromEntries(response.headers.entries()), null, 2));
|
|
274
274
|
if (!response.ok) {
|
|
275
275
|
const errorText = yield response.text();
|
|
276
276
|
logger_1.logger.error(`Stream request failed: ${response.status} ${response.statusText}`);
|
|
277
|
-
logger_1.logger.
|
|
278
|
-
throw new Error(`Stream request failed: ${response.status} ${response.statusText}`);
|
|
277
|
+
logger_1.logger.error(`Error response: ${errorText}`);
|
|
278
|
+
throw new Error(`Stream request failed: ${response.status} ${response.statusText} - ${errorText}`);
|
|
279
279
|
}
|
|
280
280
|
if (!response.body) {
|
|
281
281
|
throw new Error('No response body received');
|