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 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 analyze "1 cup rice"
15
+ avocavo ingredient "1 cup rice"
16
16
 
17
17
  # Analyze a recipe
18
- avocavo recipe "2 eggs, 1 cup flour, 1 cup milk"
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 analyze <ingredient>`
56
+ ### `avocavo ingredient <ingredient>`
57
57
  Analyze a single ingredient:
58
58
  ```bash
59
- avocavo analyze "1 cup brown rice"
60
- avocavo analyze "200g chicken breast"
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 <ingredients>`
64
+ ### `avocavo recipe [options]`
64
65
  Analyze multiple ingredients as a recipe:
65
66
  ```bash
66
- avocavo recipe "2 eggs, 1 cup flour, 1/2 cup milk"
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 <file>`
70
- Analyze ingredients from a file:
72
+ ### `avocavo batch [options]`
73
+ Analyze multiple ingredients efficiently:
71
74
  ```bash
72
- avocavo batch ingredients.txt
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 whoami`
76
- Show current authenticated user.
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 analyze "1 cup rice" --json
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 analyze "1 cup rice"
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
- // Test the selected API key
81
- const apiKey = await auth.getApiKey();
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 retrieved: ${apiKey.substring(0, 12)}...`));
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
- // If we can't fetch account info, it's likely because the API key is invalid or missing
93
- // This happens for new users who don't have any API keys yet
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
- console.log(chalk.yellow('⚠️ You don\'t have any API keys yet'));
107
- console.log(chalk.cyan('💡 Create your first API key to start using the nutrition API:'));
108
- console.log(chalk.cyan(' avocavo keys create'));
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 jwtToken = data.jwt_token;
174
+ const oauthToken = data.auth_uuid; // This is the OAuth session token
175
175
 
176
- if (!jwtToken) {
177
- console.error(chalk.red(' No JWT token received from server'));
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'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "avocavo",
3
- "version": "1.0.1",
3
+ "version": "1.0.4",
4
4
  "description": "Avocavo CLI - Nutrition analysis made simple. Get accurate USDA nutrition data with secure authentication.",
5
5
  "main": "index.js",
6
6
  "bin": {