avocavo 1.0.1 → 1.0.4
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/CHANGELOG.md +55 -0
- package/README.md +31 -14
- package/bin/avocavo.js +61 -16
- package/lib/auth.js +63 -27
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,61 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the Avocavo CLI will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.0.4] - 2025-08-04
|
|
6
|
+
|
|
7
|
+
### 🔧 Login Experience Fixes
|
|
8
|
+
- **FIXED**: Login no longer incorrectly reports "no API keys" when user has existing keys
|
|
9
|
+
- **IMPROVED**: Smart API key detection after login - checks server for all user keys
|
|
10
|
+
- **ADDED**: Auto-selection of single API key for seamless experience
|
|
11
|
+
- **ENHANCED**: Better messaging for users with multiple API keys
|
|
12
|
+
|
|
13
|
+
### 🎯 Key Management Improvements
|
|
14
|
+
- **SMART**: If user has exactly 1 API key, it's automatically selected and activated
|
|
15
|
+
- **GUIDANCE**: Users with multiple keys get clear instructions to run `avocavo keys switch`
|
|
16
|
+
- **ACCURATE**: Login now correctly identifies users with 0, 1, or multiple API keys
|
|
17
|
+
- **RELIABLE**: Fallback handling if API key check fails
|
|
18
|
+
|
|
19
|
+
### 🛠️ Technical Changes
|
|
20
|
+
- Enhanced post-login API key detection logic
|
|
21
|
+
- Added server-side key enumeration via `/api/keys` endpoint
|
|
22
|
+
- Improved error handling and user guidance messages
|
|
23
|
+
- Better integration between authentication and key management systems
|
|
24
|
+
|
|
25
|
+
## [1.0.3] - 2025-08-04
|
|
26
|
+
|
|
27
|
+
### 🔐 Authentication Fixes
|
|
28
|
+
- **CRITICAL**: Fixed OAuth authentication with proper JWT token exchange
|
|
29
|
+
- **FIXED**: Resolved "Invalid or expired OAuth session" errors
|
|
30
|
+
- **IMPROVED**: Enhanced token exchange flow with industry-standard implementation
|
|
31
|
+
- **ADDED**: Automatic OAuth-to-JWT token exchange after login
|
|
32
|
+
|
|
33
|
+
### 🛡️ Security Enhancements
|
|
34
|
+
- Implemented OAuth 2.0 Token Exchange (RFC 8693) standards
|
|
35
|
+
- Proper JWT token validation with HMAC-SHA256
|
|
36
|
+
- Secure token storage with automatic migration
|
|
37
|
+
- Enhanced error handling for authentication failures
|
|
38
|
+
|
|
39
|
+
### 🔧 Technical Changes
|
|
40
|
+
- Added token exchange endpoint integration
|
|
41
|
+
- Updated authentication flow to use Supabase JWT tokens
|
|
42
|
+
- Improved error messages for auth failures
|
|
43
|
+
- Better handling of token expiry and refresh
|
|
44
|
+
|
|
45
|
+
## [1.0.2] - 2025-01-30
|
|
46
|
+
|
|
47
|
+
### 📚 Documentation Updates
|
|
48
|
+
- **FIXED**: Updated README.md with correct CLI command examples
|
|
49
|
+
- **CORRECTED**: Changed deprecated `avocavo analyze` to `avocavo ingredient`
|
|
50
|
+
- **ENHANCED**: Added comprehensive command examples for all operations
|
|
51
|
+
- **IMPROVED**: Updated recipe command syntax to show proper `-i` flag usage
|
|
52
|
+
- **ADDED**: Complete API key management command documentation
|
|
53
|
+
|
|
54
|
+
### 🔧 Command Corrections
|
|
55
|
+
- All examples now use the correct `avocavo ingredient` command (not `analyze`)
|
|
56
|
+
- Recipe examples show proper `-i` flag for ingredients input
|
|
57
|
+
- Added missing API key management examples (`avocavo keys create`, `switch`, etc.)
|
|
58
|
+
- Updated help text to match actual CLI functionality
|
|
59
|
+
|
|
5
60
|
## [1.0.1] - 2025-01-30
|
|
6
61
|
|
|
7
62
|
### 🚨 Critical Security Fixes
|
package/README.md
CHANGED
|
@@ -12,10 +12,10 @@ npm install -g avocavo
|
|
|
12
12
|
avocavo login
|
|
13
13
|
|
|
14
14
|
# Analyze a single ingredient
|
|
15
|
-
avocavo
|
|
15
|
+
avocavo ingredient "1 cup rice"
|
|
16
16
|
|
|
17
17
|
# Analyze a recipe
|
|
18
|
-
avocavo recipe "2 eggs
|
|
18
|
+
avocavo recipe -i "2 eggs" "1 cup flour" "1 cup milk"
|
|
19
19
|
|
|
20
20
|
# Get help
|
|
21
21
|
avocavo --help
|
|
@@ -53,27 +53,44 @@ npm install -g avocavo
|
|
|
53
53
|
### `avocavo login`
|
|
54
54
|
Authenticate with your Avocavo account via secure OAuth.
|
|
55
55
|
|
|
56
|
-
### `avocavo
|
|
56
|
+
### `avocavo ingredient <ingredient>`
|
|
57
57
|
Analyze a single ingredient:
|
|
58
58
|
```bash
|
|
59
|
-
avocavo
|
|
60
|
-
avocavo
|
|
59
|
+
avocavo ingredient "1 cup brown rice"
|
|
60
|
+
avocavo ingredient "200g chicken breast"
|
|
61
|
+
avocavo ingredient "1 cup rice" -v # Include USDA verification URL
|
|
61
62
|
```
|
|
62
63
|
|
|
63
|
-
### `avocavo recipe
|
|
64
|
+
### `avocavo recipe [options]`
|
|
64
65
|
Analyze multiple ingredients as a recipe:
|
|
65
66
|
```bash
|
|
66
|
-
avocavo recipe "2 eggs
|
|
67
|
+
avocavo recipe -i "2 eggs" "1 cup flour" "1/2 cup milk"
|
|
68
|
+
avocavo recipe -i "2 cups flour" "1 cup milk" -s 8 # 8 servings
|
|
69
|
+
avocavo recipe -f ingredients.txt -s 4 # From file, 4 servings
|
|
67
70
|
```
|
|
68
71
|
|
|
69
|
-
### `avocavo batch
|
|
70
|
-
Analyze ingredients
|
|
72
|
+
### `avocavo batch [options]`
|
|
73
|
+
Analyze multiple ingredients efficiently:
|
|
71
74
|
```bash
|
|
72
|
-
avocavo batch
|
|
75
|
+
avocavo batch -i "1 cup rice" "2 tbsp oil" "4 oz chicken"
|
|
76
|
+
avocavo batch -f ingredients.txt
|
|
73
77
|
```
|
|
74
78
|
|
|
75
|
-
### `avocavo
|
|
76
|
-
|
|
79
|
+
### `avocavo keys`
|
|
80
|
+
Manage API keys:
|
|
81
|
+
```bash
|
|
82
|
+
avocavo keys list # List all API keys
|
|
83
|
+
avocavo keys create # Create new API key
|
|
84
|
+
avocavo keys create -n "Production" # Create with custom name
|
|
85
|
+
avocavo keys switch # Switch active API key
|
|
86
|
+
avocavo keys delete # Delete an API key
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### `avocavo status`
|
|
90
|
+
Show login status and account information.
|
|
91
|
+
|
|
92
|
+
### `avocavo health`
|
|
93
|
+
Check API health and status.
|
|
77
94
|
|
|
78
95
|
### `avocavo logout`
|
|
79
96
|
Remove stored credentials.
|
|
@@ -99,7 +116,7 @@ Remove stored credentials.
|
|
|
99
116
|
|
|
100
117
|
### JSON Format
|
|
101
118
|
```bash
|
|
102
|
-
avocavo
|
|
119
|
+
avocavo ingredient "1 cup rice" --json
|
|
103
120
|
```
|
|
104
121
|
|
|
105
122
|
## 🆘 Support
|
|
@@ -127,5 +144,5 @@ npm install -g avocavo
|
|
|
127
144
|
|
|
128
145
|
# Same commands work!
|
|
129
146
|
avocavo login
|
|
130
|
-
avocavo
|
|
147
|
+
avocavo ingredient "1 cup rice"
|
|
131
148
|
```
|
package/bin/avocavo.js
CHANGED
|
@@ -4,6 +4,7 @@ const { program } = require('commander');
|
|
|
4
4
|
const chalk = require('chalk');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const fs = require('fs');
|
|
7
|
+
const axios = require('axios');
|
|
7
8
|
const { NutritionAPI } = require('../lib/api');
|
|
8
9
|
const { AuthManager } = require('../lib/auth');
|
|
9
10
|
const { formatNutrition, formatPerformanceMetrics, formatUSDAMatch, formatTable } = require('../lib/formatters');
|
|
@@ -77,11 +78,13 @@ program
|
|
|
77
78
|
if (success) {
|
|
78
79
|
console.log(chalk.green('✅ Successfully logged in!'));
|
|
79
80
|
|
|
80
|
-
//
|
|
81
|
-
|
|
81
|
+
// Check for API keys - first check if user has a selected key
|
|
82
|
+
let apiKey = await auth.getApiKey();
|
|
83
|
+
|
|
82
84
|
if (apiKey) {
|
|
85
|
+
// User has a selected API key, test it
|
|
83
86
|
if (program.opts().verbose) {
|
|
84
|
-
console.log(chalk.gray(`Debug: API key
|
|
87
|
+
console.log(chalk.gray(`Debug: Using selected API key: ${apiKey.substring(0, 12)}...`));
|
|
85
88
|
}
|
|
86
89
|
const api = new NutritionAPI(apiKey, program.opts().baseUrl, 30000, auth);
|
|
87
90
|
try {
|
|
@@ -89,23 +92,66 @@ program
|
|
|
89
92
|
console.log(chalk.cyan(`📊 Account: ${account.email} (${account.api_tier} tier)`));
|
|
90
93
|
console.log(chalk.cyan(`📈 Usage: ${account.usage.current_month}/${account.usage.monthly_limit || 'unlimited'}`));
|
|
91
94
|
} catch (err) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
console.log(chalk.yellow('⚠️ You don\'t have any API keys yet'));
|
|
95
|
-
console.log(chalk.cyan('💡 Create your first API key to start using the nutrition API:'));
|
|
96
|
-
console.log(chalk.cyan(' avocavo keys create'));
|
|
95
|
+
console.log(chalk.yellow('⚠️ Selected API key appears invalid'));
|
|
96
|
+
console.log(chalk.cyan('💡 Try selecting a different key with: avocavo keys switch'));
|
|
97
97
|
if (program.opts().verbose) {
|
|
98
98
|
console.log(chalk.gray(`Debug: ${err.message}`));
|
|
99
|
-
console.log(chalk.gray(`Debug: Status ${err.statusCode || 'unknown'}`));
|
|
100
|
-
if (err.response?.data) {
|
|
101
|
-
console.log(chalk.gray(`Response: ${JSON.stringify(err.response.data, null, 2)}`));
|
|
102
|
-
}
|
|
103
99
|
}
|
|
104
100
|
}
|
|
105
101
|
} else {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
102
|
+
// No selected API key, check if user has any keys at all
|
|
103
|
+
try {
|
|
104
|
+
const KeyManager = require('../lib/keys');
|
|
105
|
+
const keyManager = new KeyManager(auth, program.opts().baseUrl);
|
|
106
|
+
|
|
107
|
+
// Get user's API keys from server
|
|
108
|
+
const headers = await keyManager.getAuthHeaders();
|
|
109
|
+
const response = await axios.get(`${program.opts().baseUrl}/api/keys`, { headers });
|
|
110
|
+
|
|
111
|
+
if (response.data.keys && response.data.keys.length > 0) {
|
|
112
|
+
const keyCount = response.data.keys.length;
|
|
113
|
+
|
|
114
|
+
if (keyCount === 1) {
|
|
115
|
+
// Auto-select the single key
|
|
116
|
+
console.log(chalk.cyan('🔄 You have 1 API key. Auto-selecting it...'));
|
|
117
|
+
const singleKey = response.data.keys[0];
|
|
118
|
+
|
|
119
|
+
// Store the key as selected (simplified approach)
|
|
120
|
+
await auth.storeApiKeySecurely(auth.getUserInfo().email, singleKey.api_key);
|
|
121
|
+
|
|
122
|
+
// Test the auto-selected key
|
|
123
|
+
const api = new NutritionAPI(singleKey.api_key, program.opts().baseUrl, 30000, auth);
|
|
124
|
+
try {
|
|
125
|
+
const account = await api.getAccountUsage();
|
|
126
|
+
console.log(chalk.green('✅ API key activated!'));
|
|
127
|
+
console.log(chalk.cyan(`📊 Account: ${account.email} (${account.api_tier} tier)`));
|
|
128
|
+
console.log(chalk.cyan(`📈 Usage: ${account.usage.current_month}/${account.usage.monthly_limit || 'unlimited'}`));
|
|
129
|
+
} catch (err) {
|
|
130
|
+
console.log(chalk.yellow('⚠️ Auto-selected API key appears invalid'));
|
|
131
|
+
console.log(chalk.cyan('💡 Check your keys with: avocavo keys list'));
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
// Multiple keys - prompt user to select one
|
|
135
|
+
console.log(chalk.cyan(`💡 You have ${keyCount} API keys but none selected. Choose one to activate:`));
|
|
136
|
+
console.log(chalk.cyan(' avocavo keys switch'));
|
|
137
|
+
console.log(chalk.gray(' or'));
|
|
138
|
+
console.log(chalk.cyan(' avocavo keys list'));
|
|
139
|
+
}
|
|
140
|
+
} else {
|
|
141
|
+
// No API keys at all
|
|
142
|
+
console.log(chalk.yellow('⚠️ You don\'t have any API keys yet'));
|
|
143
|
+
console.log(chalk.cyan('💡 Create your first API key to start using the nutrition API:'));
|
|
144
|
+
console.log(chalk.cyan(' avocavo keys create'));
|
|
145
|
+
}
|
|
146
|
+
} catch (keyCheckError) {
|
|
147
|
+
// Fallback to create message if we can't check existing keys
|
|
148
|
+
console.log(chalk.yellow('⚠️ Unable to check for existing API keys'));
|
|
149
|
+
console.log(chalk.cyan('💡 Create an API key to start using the nutrition API:'));
|
|
150
|
+
console.log(chalk.cyan(' avocavo keys create'));
|
|
151
|
+
if (program.opts().verbose) {
|
|
152
|
+
console.log(chalk.gray(`Debug: ${keyCheckError.message}`));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
109
155
|
}
|
|
110
156
|
} else {
|
|
111
157
|
console.log(chalk.red('❌ Login failed'));
|
|
@@ -648,7 +694,6 @@ program
|
|
|
648
694
|
if (result.performance) {
|
|
649
695
|
console.log(chalk.bold('⚡ Performance:'));
|
|
650
696
|
console.log(` 📊 Avg Response: ${result.performance.avg_response_time_ms}ms`);
|
|
651
|
-
console.log(` 💾 Cache Hit Rate: ${result.performance.cache_hit_rate}%`);
|
|
652
697
|
|
|
653
698
|
if (result.performance.uptime) {
|
|
654
699
|
console.log(` ⏱️ Uptime: ${result.performance.uptime}`);
|
package/lib/auth.js
CHANGED
|
@@ -171,37 +171,73 @@ class AuthManager {
|
|
|
171
171
|
|
|
172
172
|
// Store credentials securely
|
|
173
173
|
const userEmail = data.user_email || data.user_info?.email;
|
|
174
|
-
const
|
|
174
|
+
const oauthToken = data.auth_uuid; // This is the OAuth session token
|
|
175
175
|
|
|
176
|
-
|
|
177
|
-
|
|
176
|
+
// Exchange OAuth token for proper JWT token
|
|
177
|
+
spinner.start('Exchanging authentication tokens...');
|
|
178
|
+
try {
|
|
179
|
+
const tokenExchangeResponse = await axios.post(
|
|
180
|
+
`${this.baseUrl}/api/auth/exchange-token`,
|
|
181
|
+
{},
|
|
182
|
+
{
|
|
183
|
+
headers: {
|
|
184
|
+
'Authorization': `Bearer auth_uuid:${oauthToken}`,
|
|
185
|
+
'Content-Type': 'application/json'
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
if (!tokenExchangeResponse.data.success) {
|
|
191
|
+
spinner.fail('Token exchange failed');
|
|
192
|
+
console.error(chalk.red(tokenExchangeResponse.data.error || 'Unknown error'));
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const jwtToken = tokenExchangeResponse.data.access_token;
|
|
197
|
+
|
|
198
|
+
if (!jwtToken) {
|
|
199
|
+
console.error(chalk.red('❌ No JWT token received from token exchange'));
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
spinner.succeed('Token exchange successful');
|
|
204
|
+
|
|
205
|
+
// Store JWT token securely for session management
|
|
206
|
+
await this.storeJwtSecurely(userEmail, jwtToken);
|
|
207
|
+
|
|
208
|
+
// Store session data (JWT-based authentication)
|
|
209
|
+
const sessionData = {
|
|
210
|
+
userInfo: {
|
|
211
|
+
email: userEmail,
|
|
212
|
+
api_tier: tokenExchangeResponse.data.user?.api_tier || 'free'
|
|
213
|
+
},
|
|
214
|
+
loginTime: Date.now(),
|
|
215
|
+
provider: data.provider || 'google',
|
|
216
|
+
hasJwt: true,
|
|
217
|
+
usesSecureStorage: this.keytarAvailable
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// Store session data
|
|
221
|
+
this.config.set('sessionData', sessionData);
|
|
222
|
+
this.config.set('isLoggedIn', true);
|
|
223
|
+
|
|
224
|
+
// Clear any old API key data since we're now JWT-based
|
|
225
|
+
this.config.delete('apiKey');
|
|
226
|
+
this.config.delete('apiKeys');
|
|
227
|
+
this.config.delete('activeKey');
|
|
228
|
+
|
|
229
|
+
console.log(chalk.green(`✅ Logged in as ${userEmail || 'Unknown'}`));
|
|
230
|
+
return true;
|
|
231
|
+
|
|
232
|
+
} catch (exchangeError) {
|
|
233
|
+
spinner.fail('Token exchange failed');
|
|
234
|
+
console.error(chalk.red(`Failed to exchange token: ${exchangeError.message}`));
|
|
235
|
+
if (exchangeError.response) {
|
|
236
|
+
console.error(chalk.red(`Server response: ${JSON.stringify(exchangeError.response.data)}`));
|
|
237
|
+
}
|
|
178
238
|
return false;
|
|
179
239
|
}
|
|
180
240
|
|
|
181
|
-
// Store JWT token securely for session management
|
|
182
|
-
await this.storeJwtSecurely(userEmail, jwtToken);
|
|
183
|
-
|
|
184
|
-
// Store session data (JWT-based authentication)
|
|
185
|
-
const sessionData = {
|
|
186
|
-
userInfo: { email: userEmail },
|
|
187
|
-
loginTime: Date.now(),
|
|
188
|
-
provider: data.provider || 'google',
|
|
189
|
-
hasJwt: true,
|
|
190
|
-
usesSecureStorage: this.keytarAvailable
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
// Store session data
|
|
194
|
-
this.config.set('sessionData', sessionData);
|
|
195
|
-
this.config.set('isLoggedIn', true);
|
|
196
|
-
|
|
197
|
-
// Clear any old API key data since we're now JWT-based
|
|
198
|
-
this.config.delete('apiKey');
|
|
199
|
-
this.config.delete('apiKeys');
|
|
200
|
-
this.config.delete('activeKey');
|
|
201
|
-
|
|
202
|
-
console.log(chalk.green(`✅ Logged in as ${userEmail || 'Unknown'}`));
|
|
203
|
-
return true;
|
|
204
|
-
|
|
205
241
|
} else if (data.status === 'failed') {
|
|
206
242
|
spinner.fail('Login failed');
|
|
207
243
|
console.error(chalk.red(data.error || 'Unknown error'));
|