@vibe-db/cli 1.2.2 → 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/CHANGELOG.md ADDED
@@ -0,0 +1,63 @@
1
+ # Changelog
2
+
3
+ All notable changes to the VibeDB CLI will be documented in this file.
4
+
5
+ ## [1.4.0] - 2026-01-12
6
+
7
+ ### Added
8
+ - **Usage Command**: New `vibedb usage` command to view storage usage and estimated costs
9
+ - Shows database storage and backup storage breakdown
10
+ - Displays estimated charges for current billing period
11
+ - Shows pricing information
12
+ - **Backup Management Commands**: Complete CLI support for backup operations
13
+ - `vibedb backup list <database_id>` - List all backups for a database
14
+ - `vibedb backup create <database_id>` - Create a manual backup
15
+ - `vibedb backup restore <database_id> <backup_id>` - Restore from backup (with confirmation)
16
+ - `vibedb backup delete <database_id> <backup_id>` - Delete a backup (with confirmation)
17
+ - Added safety confirmations for destructive operations (restore, delete)
18
+ - Improved error messages for missing authentication
19
+
20
+ ### Changed
21
+ - Enhanced API module with backup-related functions
22
+ - Updated README with comprehensive documentation for new commands
23
+
24
+ ### Fixed
25
+ - Fixed handling of undefined estimated charges in usage display
26
+
27
+ ## [1.3.0] - 2026-01-11
28
+
29
+ ### Added
30
+ - **Team Support**: Multi-team functionality
31
+ - `vibedb teams list` - List all teams
32
+ - `vibedb teams switch <team_id>` - Switch between teams
33
+ - `vibedb teams current` - Show current team
34
+ - Auto-selects personal team on login
35
+ - **Billing Commands**: Stripe billing integration
36
+ - `vibedb billing info` - View billing information
37
+ - `vibedb billing subscribe` - Subscribe to VibeDB
38
+ - `vibedb billing cancel` - Cancel subscription
39
+ - `vibedb billing invoices` - View invoices
40
+
41
+ ### Changed
42
+ - Database operations now scoped to current team
43
+ - Config file now stores current team ID
44
+
45
+ ## [1.2.0] - Earlier
46
+
47
+ ### Added
48
+ - Basic database operations
49
+ - `vibedb list` - List databases
50
+ - `vibedb init` - Download VIBEDB.md prompt file
51
+ - Authentication commands
52
+ - `vibedb signup` - Create account
53
+ - `vibedb login` - Login to existing account
54
+ - `vibedb logout` - Logout and clear credentials
55
+ - Configuration management in `~/.vibedb`
56
+
57
+ ## Future Plans
58
+
59
+ - Database creation directly from CLI
60
+ - Database deletion with recovery options
61
+ - Connection testing
62
+ - Query execution from CLI
63
+ - Import/export utilities
package/README.md CHANGED
@@ -99,6 +99,177 @@ List all your databases.
99
99
 
100
100
  **Output:**
101
101
  - Table showing all databases with ID, Name, Type, and Status
102
+ - Note: Shows databases for your current team only
103
+
104
+ ### `vibedb teams list` (alias: `teams ls`)
105
+ List all teams you belong to.
106
+
107
+ **Requirements:**
108
+ - Must be logged in
109
+
110
+ **Output:**
111
+ - Table showing team ID, name, role, type, and current indicator
112
+ - Shows which team is currently selected
113
+
114
+ ### `vibedb teams switch <team_id>`
115
+ Switch to a different team.
116
+
117
+ **Requirements:**
118
+ - Must be logged in
119
+ - Team ID from `vibedb teams list`
120
+
121
+ **Example:**
122
+ ```bash
123
+ vibedb teams switch team_abc123
124
+ ```
125
+
126
+ **Output:**
127
+ - Confirmation of team switch
128
+ - All subsequent commands use this team context
129
+
130
+ ### `vibedb teams current`
131
+ Show currently selected team.
132
+
133
+ **Requirements:**
134
+ - Must be logged in
135
+
136
+ **Output:**
137
+ - Current team details (ID, name, role, type)
138
+
139
+ ### `vibedb usage`
140
+ Show current usage and costs.
141
+
142
+ **Requirements:**
143
+ - Must be logged in
144
+
145
+ **Output:**
146
+ - Storage breakdown (database + backup storage)
147
+ - Estimated charges for current period
148
+ - Pricing information
149
+
150
+ **Example:**
151
+ ```bash
152
+ vibedb usage
153
+
154
+ 📊 Usage & Costs
155
+
156
+ Storage Usage:
157
+ Database Storage: 45.23 MB
158
+ Backup Storage: 12.67 MB
159
+ ─────────────────
160
+ Total Storage: 57.90 MB
161
+
162
+ Estimated Charges:
163
+ Current Period: $0.0823 USD
164
+
165
+ Pricing:
166
+ Storage: $0.00014 per GB-hour
167
+ Compute: $0.000167 per minute
168
+ ```
169
+
170
+ ### `vibedb backup list <database_id>`
171
+ List all backups for a database.
172
+
173
+ **Requirements:**
174
+ - Must be logged in
175
+ - Valid database ID
176
+
177
+ **Output:**
178
+ - List of backups with ID, type (auto/manual), size, and creation date
179
+
180
+ **Example:**
181
+ ```bash
182
+ vibedb backup list db_abc123
183
+
184
+ 💾 Backups
185
+
186
+ Found 3 backup(s):
187
+
188
+ 1. d4339369-a3bb-492c-afe3-2124265e9b56
189
+ Type: [Manual]
190
+ Size: 12.34 MB
191
+ Created: 1/12/2026, 8:30:06 PM
192
+
193
+ 2. a1234567-b8cd-9012-efgh-345678901234
194
+ Type: [Auto]
195
+ Size: 11.98 MB
196
+ Created: 1/12/2026, 2:00:00 AM
197
+ ```
198
+
199
+ ### `vibedb backup create <database_id>`
200
+ Create a manual backup.
201
+
202
+ **Requirements:**
203
+ - Must be logged in
204
+ - Valid database ID
205
+
206
+ **Output:**
207
+ - Backup ID and creation timestamp
208
+
209
+ **Example:**
210
+ ```bash
211
+ vibedb backup create db_abc123
212
+
213
+ ✓ Backup created successfully!
214
+
215
+ Backup ID: d4339369-a3bb-492c-afe3-2124265e9b56
216
+ Type: [Manual]
217
+ Created: 1/12/2026, 8:30:06 PM
218
+ Size: 12.34 MB
219
+
220
+ Restore this backup with:
221
+ vibedb backup restore db_abc123 d4339369-a3bb-492c-afe3-2124265e9b56
222
+ ```
223
+
224
+ ### `vibedb backup restore <database_id> <backup_id>`
225
+ Restore a database from a backup.
226
+
227
+ **Requirements:**
228
+ - Must be logged in
229
+ - Valid database ID and backup ID
230
+ - Confirmation required (interactive)
231
+
232
+ **Warning:** This will replace ALL current data in the database!
233
+
234
+ **Example:**
235
+ ```bash
236
+ vibedb backup restore db_abc123 d4339369-a3bb-492c-afe3-2124265e9b56
237
+
238
+ 🔄 Restore Backup
239
+
240
+ ⚠️ WARNING:
241
+ This will replace ALL current data in the database with the backup.
242
+ This action cannot be undone.
243
+
244
+ Database ID: db_abc123
245
+ Backup ID: d4339369-a3bb-492c-afe3-2124265e9b56
246
+
247
+ Are you sure you want to restore this backup? (y/N): y
248
+
249
+ ✓ Backup restored successfully!
250
+ ```
251
+
252
+ ### `vibedb backup delete <database_id> <backup_id>`
253
+ Delete a backup.
254
+
255
+ **Requirements:**
256
+ - Must be logged in
257
+ - Valid database ID and backup ID
258
+ - Confirmation required (interactive)
259
+
260
+ **Example:**
261
+ ```bash
262
+ vibedb backup delete db_abc123 d4339369-a3bb-492c-afe3-2124265e9b56
263
+
264
+ 🗑️ Delete Backup
265
+
266
+ Database ID: db_abc123
267
+ Backup ID: d4339369-a3bb-492c-afe3-2124265e9b56
268
+
269
+ Are you sure you want to delete this backup? (y/N): y
270
+
271
+ ✓ Backup deleted successfully!
272
+ ```
102
273
 
103
274
  ## Configuration
104
275
 
@@ -108,10 +279,13 @@ The CLI stores your credentials in `~/.vibedb`:
108
279
  {
109
280
  "api_key": "vdb_xxx...",
110
281
  "email": "you@example.com",
111
- "api_url": "https://api.vibedb.dev"
282
+ "api_url": "https://api.vibedb.dev",
283
+ "current_team_id": "team_abc123"
112
284
  }
113
285
  ```
114
286
 
287
+ **Team Context:** The CLI remembers your current team selection. Use `vibedb teams switch` to change teams.
288
+
115
289
  **Security Note:** Keep this file secure. Anyone with your API key can manage your databases.
116
290
 
117
291
  ## Examples
@@ -150,6 +324,50 @@ $ npx @vibedb/cli list
150
324
  Total: 1 database
151
325
  ```
152
326
 
327
+ ### Working with Teams
328
+ ```bash
329
+ # List all your teams
330
+ $ vibedb teams list
331
+
332
+ ┌──────────────────┬──────────────┬────────┬──────────┬─────────┐
333
+ │ ID │ Name │ Role │ Type │ Current │
334
+ ├──────────────────┼──────────────┼────────┼──────────┼─────────┤
335
+ │ team_abc123 │ John's Team │ owner │ Personal │ ✓ │
336
+ │ team_xyz789 │ Acme Corp │ member │ Team │ │
337
+ └──────────────────┴──────────────┴────────┴──────────┴─────────┘
338
+
339
+ Total: 2 teams
340
+
341
+ # Switch to a different team
342
+ $ vibedb teams switch team_xyz789
343
+
344
+ ✓ Successfully switched to team
345
+ Team: Acme Corp
346
+ Role: member
347
+
348
+ # List databases in the new team context
349
+ $ vibedb list
350
+
351
+ ┌─────────────────┬────────────────┬──────────┬────────┐
352
+ │ ID │ Name │ Type │ Status │
353
+ ├─────────────────┼────────────────┼──────────┼────────┤
354
+ │ db_def456 │ production-db │ postgres │ ready │
355
+ │ db_ghi789 │ staging-cache │ redis │ ready │
356
+ └─────────────────┴────────────────┴──────────┴────────┘
357
+
358
+ Total: 2 databases
359
+
360
+ # Check current team
361
+ $ vibedb teams current
362
+
363
+ 📍 Current Team
364
+
365
+ ID: team_xyz789
366
+ Name: Acme Corp
367
+ Role: member
368
+ Type: Team
369
+ ```
370
+
153
371
  ## Supported Databases
154
372
 
155
373
  - **PostgreSQL** - General purpose relational database
@@ -191,7 +409,12 @@ The CLI interacts with these VibeDB API endpoints:
191
409
  - `POST /v1/auth/login` - Authenticate
192
410
  - `GET /v1/databases` - List databases
193
411
  - `GET /v1/prompt-file` - Download prompt file
194
- - `GET /v1/account` - Get account info
412
+ - `GET /v1/account` - Get account info and usage
413
+ - `GET /v1/teams` - List teams
414
+ - `GET /v1/databases/{id}/backups` - List backups
415
+ - `POST /v1/databases/{id}/backups` - Create backup
416
+ - `POST /v1/databases/{id}/backups/{backup_id}/restore` - Restore backup
417
+ - `DELETE /v1/databases/{id}/backups/{backup_id}` - Delete backup
195
418
 
196
419
  Full API docs: https://api.vibedb.dev/docs
197
420
 
package/bin/vibedb.js CHANGED
@@ -12,6 +12,14 @@ const billingInfoCommand = require('../src/commands/billing-info');
12
12
  const billingSubscribeCommand = require('../src/commands/billing-subscribe');
13
13
  const billingCancelCommand = require('../src/commands/billing-cancel');
14
14
  const billingInvoicesCommand = require('../src/commands/billing-invoices');
15
+ const teamsListCommand = require('../src/commands/teams-list');
16
+ const teamsSwitchCommand = require('../src/commands/teams-switch');
17
+ const teamsCurrentCommand = require('../src/commands/teams-current');
18
+ const usageCommand = require('../src/commands/usage');
19
+ const backupListCommand = require('../src/commands/backup-list');
20
+ const backupCreateCommand = require('../src/commands/backup-create');
21
+ const backupRestoreCommand = require('../src/commands/backup-restore');
22
+ const backupDeleteCommand = require('../src/commands/backup-delete');
15
23
 
16
24
  const program = new Command();
17
25
 
@@ -134,6 +142,114 @@ billing
134
142
  }
135
143
  });
136
144
 
145
+ // Teams commands
146
+ const teams = program
147
+ .command('teams')
148
+ .description('Manage teams and team membership');
149
+
150
+ teams
151
+ .command('list')
152
+ .alias('ls')
153
+ .description('List all teams you belong to')
154
+ .action(async () => {
155
+ try {
156
+ await teamsListCommand();
157
+ } catch (error) {
158
+ console.error(chalk.red('Unexpected error:'), error.message);
159
+ process.exit(1);
160
+ }
161
+ });
162
+
163
+ teams
164
+ .command('switch <team_id>')
165
+ .description('Switch to a different team')
166
+ .action(async (teamId) => {
167
+ try {
168
+ await teamsSwitchCommand(teamId);
169
+ } catch (error) {
170
+ console.error(chalk.red('Unexpected error:'), error.message);
171
+ process.exit(1);
172
+ }
173
+ });
174
+
175
+ teams
176
+ .command('current')
177
+ .description('Show current team')
178
+ .action(async () => {
179
+ try {
180
+ await teamsCurrentCommand();
181
+ } catch (error) {
182
+ console.error(chalk.red('Unexpected error:'), error.message);
183
+ process.exit(1);
184
+ }
185
+ });
186
+
187
+ // Usage command
188
+ program
189
+ .command('usage')
190
+ .description('Show current usage and costs')
191
+ .action(async () => {
192
+ try {
193
+ await usageCommand();
194
+ } catch (error) {
195
+ console.error(chalk.red('Unexpected error:'), error.message);
196
+ process.exit(1);
197
+ }
198
+ });
199
+
200
+ // Backup commands
201
+ const backup = program
202
+ .command('backup')
203
+ .description('Manage database backups');
204
+
205
+ backup
206
+ .command('list <database_id>')
207
+ .description('List all backups for a database')
208
+ .action(async (databaseId) => {
209
+ try {
210
+ await backupListCommand(databaseId);
211
+ } catch (error) {
212
+ console.error(chalk.red('Unexpected error:'), error.message);
213
+ process.exit(1);
214
+ }
215
+ });
216
+
217
+ backup
218
+ .command('create <database_id>')
219
+ .description('Create a manual backup')
220
+ .action(async (databaseId) => {
221
+ try {
222
+ await backupCreateCommand(databaseId);
223
+ } catch (error) {
224
+ console.error(chalk.red('Unexpected error:'), error.message);
225
+ process.exit(1);
226
+ }
227
+ });
228
+
229
+ backup
230
+ .command('restore <database_id> <backup_id>')
231
+ .description('Restore a database from a backup')
232
+ .action(async (databaseId, backupId) => {
233
+ try {
234
+ await backupRestoreCommand(databaseId, backupId);
235
+ } catch (error) {
236
+ console.error(chalk.red('Unexpected error:'), error.message);
237
+ process.exit(1);
238
+ }
239
+ });
240
+
241
+ backup
242
+ .command('delete <database_id> <backup_id>')
243
+ .description('Delete a backup')
244
+ .action(async (databaseId, backupId) => {
245
+ try {
246
+ await backupDeleteCommand(databaseId, backupId);
247
+ } catch (error) {
248
+ console.error(chalk.red('Unexpected error:'), error.message);
249
+ process.exit(1);
250
+ }
251
+ });
252
+
137
253
  // Global error handler
138
254
  process.on('unhandledRejection', (error) => {
139
255
  console.error(chalk.red.bold('\n✗ Unhandled error:'), error.message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe-db/cli",
3
- "version": "1.2.2",
3
+ "version": "1.4.0",
4
4
  "description": "Command-line interface for VibeDB - instant database provisioning for AI-assisted development",
5
5
  "main": "bin/vibedb.js",
6
6
  "bin": {
package/src/api.js CHANGED
@@ -43,9 +43,13 @@ async function login(email, password) {
43
43
  /**
44
44
  * List all databases for authenticated user
45
45
  */
46
- async function listDatabases(apiKey) {
46
+ async function listDatabases(apiKey, teamId = null) {
47
47
  try {
48
- const response = await axios.get(`${API_BASE_URL}/v1/databases`, {
48
+ const url = teamId
49
+ ? `${API_BASE_URL}/v1/databases?team_id=${teamId}`
50
+ : `${API_BASE_URL}/v1/databases`;
51
+
52
+ const response = await axios.get(url, {
49
53
  headers: {
50
54
  Authorization: `Bearer ${apiKey}`,
51
55
  },
@@ -78,9 +82,13 @@ async function getPromptFile() {
78
82
  /**
79
83
  * Get account information
80
84
  */
81
- async function getAccount(apiKey) {
85
+ async function getAccount(apiKey, teamId = null) {
82
86
  try {
83
- const response = await axios.get(`${API_BASE_URL}/v1/account`, {
87
+ const url = teamId
88
+ ? `${API_BASE_URL}/v1/account?team_id=${teamId}`
89
+ : `${API_BASE_URL}/v1/account`;
90
+
91
+ const response = await axios.get(url, {
84
92
  headers: {
85
93
  Authorization: `Bearer ${apiKey}`,
86
94
  },
@@ -215,6 +223,137 @@ async function pollDeviceAuth(deviceCode) {
215
223
  }
216
224
  }
217
225
 
226
+ /**
227
+ * List all teams for authenticated user
228
+ */
229
+ async function listTeams(apiKey) {
230
+ try {
231
+ const response = await axios.get(`${API_BASE_URL}/v1/teams`, {
232
+ headers: {
233
+ Authorization: `Bearer ${apiKey}`,
234
+ },
235
+ });
236
+ return response.data.teams || [];
237
+ } catch (error) {
238
+ if (error.response) {
239
+ const { error: errorCode, message } = error.response.data;
240
+ throw new Error(message || errorCode || 'Failed to list teams');
241
+ }
242
+ throw new Error(`Network error: ${error.message}`);
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Get team details
248
+ */
249
+ async function getTeam(apiKey, teamId) {
250
+ try {
251
+ const response = await axios.get(`${API_BASE_URL}/v1/teams/${teamId}`, {
252
+ headers: {
253
+ Authorization: `Bearer ${apiKey}`,
254
+ },
255
+ });
256
+ return response.data;
257
+ } catch (error) {
258
+ if (error.response) {
259
+ const { error: errorCode, message } = error.response.data;
260
+ throw new Error(message || errorCode || 'Failed to get team');
261
+ }
262
+ throw new Error(`Network error: ${error.message}`);
263
+ }
264
+ }
265
+
266
+ /**
267
+ * List backups for a database
268
+ */
269
+ async function listBackups(apiKey, databaseId) {
270
+ try {
271
+ const response = await axios.get(`${API_BASE_URL}/v1/databases/${databaseId}/backups`, {
272
+ headers: {
273
+ Authorization: `Bearer ${apiKey}`,
274
+ },
275
+ });
276
+ return response.data.backups || [];
277
+ } catch (error) {
278
+ if (error.response) {
279
+ const { error: errorCode, message } = error.response.data;
280
+ throw new Error(message || errorCode || 'Failed to list backups');
281
+ }
282
+ throw new Error(`Network error: ${error.message}`);
283
+ }
284
+ }
285
+
286
+ /**
287
+ * Create a backup for a database
288
+ */
289
+ async function createBackup(apiKey, databaseId) {
290
+ try {
291
+ const response = await axios.post(
292
+ `${API_BASE_URL}/v1/databases/${databaseId}/backups`,
293
+ {},
294
+ {
295
+ headers: {
296
+ Authorization: `Bearer ${apiKey}`,
297
+ },
298
+ }
299
+ );
300
+ return response.data;
301
+ } catch (error) {
302
+ if (error.response) {
303
+ const { error: errorCode, message } = error.response.data;
304
+ throw new Error(message || errorCode || 'Failed to create backup');
305
+ }
306
+ throw new Error(`Network error: ${error.message}`);
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Restore a database from a backup
312
+ */
313
+ async function restoreBackup(apiKey, databaseId, backupId) {
314
+ try {
315
+ const response = await axios.post(
316
+ `${API_BASE_URL}/v1/databases/${databaseId}/backups/${backupId}/restore`,
317
+ {},
318
+ {
319
+ headers: {
320
+ Authorization: `Bearer ${apiKey}`,
321
+ },
322
+ }
323
+ );
324
+ return response.data;
325
+ } catch (error) {
326
+ if (error.response) {
327
+ const { error: errorCode, message } = error.response.data;
328
+ throw new Error(message || errorCode || 'Failed to restore backup');
329
+ }
330
+ throw new Error(`Network error: ${error.message}`);
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Delete a backup
336
+ */
337
+ async function deleteBackup(apiKey, databaseId, backupId) {
338
+ try {
339
+ const response = await axios.delete(
340
+ `${API_BASE_URL}/v1/databases/${databaseId}/backups/${backupId}`,
341
+ {
342
+ headers: {
343
+ Authorization: `Bearer ${apiKey}`,
344
+ },
345
+ }
346
+ );
347
+ return response.data;
348
+ } catch (error) {
349
+ if (error.response) {
350
+ const { error: errorCode, message } = error.response.data;
351
+ throw new Error(message || errorCode || 'Failed to delete backup');
352
+ }
353
+ throw new Error(`Network error: ${error.message}`);
354
+ }
355
+ }
356
+
218
357
  module.exports = {
219
358
  signup,
220
359
  login,
@@ -227,4 +366,10 @@ module.exports = {
227
366
  getInvoices,
228
367
  startDeviceAuth,
229
368
  pollDeviceAuth,
369
+ listTeams,
370
+ getTeam,
371
+ listBackups,
372
+ createBackup,
373
+ restoreBackup,
374
+ deleteBackup,
230
375
  };
@@ -0,0 +1,48 @@
1
+ const chalk = require('chalk');
2
+ const api = require('../api');
3
+ const config = require('../config');
4
+
5
+ async function backupCreateCommand(databaseId) {
6
+ console.log(chalk.blue.bold('\n💾 Create Backup\n'));
7
+
8
+ try {
9
+ // Check if logged in
10
+ if (!config.isLoggedIn()) {
11
+ console.error(chalk.red.bold('✗ Not logged in'));
12
+ console.log(chalk.gray('\nPlease run'), chalk.cyan('vibedb login'), chalk.gray('or'), chalk.cyan('vibedb signup'), chalk.gray('first.'));
13
+ process.exit(1);
14
+ }
15
+
16
+ if (!databaseId) {
17
+ console.error(chalk.red.bold('✗ Database ID is required'));
18
+ console.log(chalk.gray('\nUsage:'), chalk.cyan('vibedb backup create <database-id>'));
19
+ process.exit(1);
20
+ }
21
+
22
+ const apiKey = config.getAPIKey();
23
+
24
+ console.log(chalk.gray(`Creating backup for database ${databaseId}...`));
25
+
26
+ const backup = await api.createBackup(apiKey, databaseId);
27
+
28
+ console.log(chalk.green.bold('\n✓ Backup created successfully!\n'));
29
+ console.log(chalk.gray('Backup ID:'), chalk.cyan(backup.id));
30
+ console.log(chalk.gray('Type:'), chalk.green('[Manual]'));
31
+ console.log(chalk.gray('Created:'), chalk.white(new Date(backup.created_at).toLocaleString()));
32
+
33
+ if (backup.size_mb) {
34
+ console.log(chalk.gray('Size:'), chalk.cyan(`${backup.size_mb.toFixed(2)} MB`));
35
+ }
36
+
37
+ console.log();
38
+ console.log(chalk.gray('Restore this backup with:'));
39
+ console.log(chalk.cyan(`vibedb backup restore ${databaseId} ${backup.id}`));
40
+ console.log();
41
+
42
+ } catch (error) {
43
+ console.error(chalk.red.bold('\n✗ Failed to create backup:'), chalk.red(error.message));
44
+ process.exit(1);
45
+ }
46
+ }
47
+
48
+ module.exports = backupCreateCommand;
@@ -0,0 +1,64 @@
1
+ const chalk = require('chalk');
2
+ const readline = require('readline');
3
+ const api = require('../api');
4
+ const config = require('../config');
5
+
6
+ async function askConfirmation(question) {
7
+ const rl = readline.createInterface({
8
+ input: process.stdin,
9
+ output: process.stdout,
10
+ });
11
+
12
+ return new Promise((resolve) => {
13
+ rl.question(question, (answer) => {
14
+ rl.close();
15
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
16
+ });
17
+ });
18
+ }
19
+
20
+ async function backupDeleteCommand(databaseId, backupId) {
21
+ console.log(chalk.blue.bold('\n🗑️ Delete Backup\n'));
22
+
23
+ try {
24
+ // Check if logged in
25
+ if (!config.isLoggedIn()) {
26
+ console.error(chalk.red.bold('✗ Not logged in'));
27
+ console.log(chalk.gray('\nPlease run'), chalk.cyan('vibedb login'), chalk.gray('or'), chalk.cyan('vibedb signup'), chalk.gray('first.'));
28
+ process.exit(1);
29
+ }
30
+
31
+ if (!databaseId || !backupId) {
32
+ console.error(chalk.red.bold('✗ Database ID and Backup ID are required'));
33
+ console.log(chalk.gray('\nUsage:'), chalk.cyan('vibedb backup delete <database-id> <backup-id>'));
34
+ process.exit(1);
35
+ }
36
+
37
+ const apiKey = config.getAPIKey();
38
+
39
+ console.log(chalk.gray('Database ID:'), chalk.cyan(databaseId));
40
+ console.log(chalk.gray('Backup ID:'), chalk.cyan(backupId));
41
+ console.log();
42
+
43
+ const confirmed = await askConfirmation(chalk.white('Are you sure you want to delete this backup? (y/N): '));
44
+
45
+ if (!confirmed) {
46
+ console.log(chalk.gray('\nDeletion cancelled.'));
47
+ return;
48
+ }
49
+
50
+ console.log(chalk.gray('\nDeleting backup...'));
51
+
52
+ await api.deleteBackup(apiKey, databaseId, backupId);
53
+
54
+ console.log(chalk.green.bold('\n✓ Backup deleted successfully!\n'));
55
+ console.log(chalk.gray('Backup ID:'), chalk.cyan(backupId));
56
+ console.log();
57
+
58
+ } catch (error) {
59
+ console.error(chalk.red.bold('\n✗ Failed to delete backup:'), chalk.red(error.message));
60
+ process.exit(1);
61
+ }
62
+ }
63
+
64
+ module.exports = backupDeleteCommand;
@@ -0,0 +1,67 @@
1
+ const chalk = require('chalk');
2
+ const api = require('../api');
3
+ const config = require('../config');
4
+
5
+ async function backupListCommand(databaseId) {
6
+ console.log(chalk.blue.bold('\n💾 Backups\n'));
7
+
8
+ try {
9
+ // Check if logged in
10
+ if (!config.isLoggedIn()) {
11
+ console.error(chalk.red.bold('✗ Not logged in'));
12
+ console.log(chalk.gray('\nPlease run'), chalk.cyan('vibedb login'), chalk.gray('or'), chalk.cyan('vibedb signup'), chalk.gray('first.'));
13
+ process.exit(1);
14
+ }
15
+
16
+ if (!databaseId) {
17
+ console.error(chalk.red.bold('✗ Database ID is required'));
18
+ console.log(chalk.gray('\nUsage:'), chalk.cyan('vibedb backup list <database-id>'));
19
+ process.exit(1);
20
+ }
21
+
22
+ const apiKey = config.getAPIKey();
23
+
24
+ console.log(chalk.gray(`Fetching backups for database ${databaseId}...`));
25
+
26
+ const backups = await api.listBackups(apiKey, databaseId);
27
+
28
+ if (backups.length === 0) {
29
+ console.log(chalk.yellow('\nNo backups found for this database.'));
30
+ console.log(chalk.gray('Create one with:'), chalk.cyan(`vibedb backup create ${databaseId}`));
31
+ return;
32
+ }
33
+
34
+ console.log();
35
+ console.log(chalk.gray(`Found ${backups.length} backup(s):\n`));
36
+
37
+ // Display backups in a table format
38
+ backups.forEach((backup, index) => {
39
+ const isAutomatic = backup.type === 'automatic';
40
+ const typeLabel = isAutomatic ? chalk.blue('[Auto]') : chalk.green('[Manual]');
41
+ const sizeLabel = backup.size_mb ? `${backup.size_mb.toFixed(2)} MB` : 'N/A';
42
+ const createdAt = new Date(backup.created_at).toLocaleString();
43
+
44
+ console.log(chalk.white.bold(`${index + 1}. ${backup.id}`));
45
+ console.log(chalk.gray(' Type:'), typeLabel);
46
+ console.log(chalk.gray(' Size:'), chalk.cyan(sizeLabel));
47
+ console.log(chalk.gray(' Created:'), chalk.white(createdAt));
48
+
49
+ if (backup.deleted_at) {
50
+ console.log(chalk.gray(' Status:'), chalk.red('Deleted'));
51
+ }
52
+
53
+ console.log();
54
+ });
55
+
56
+ console.log(chalk.gray('Commands:'));
57
+ console.log(chalk.gray(' Restore:'), chalk.cyan(`vibedb backup restore ${databaseId} <backup-id>`));
58
+ console.log(chalk.gray(' Delete:'), chalk.cyan(`vibedb backup delete ${databaseId} <backup-id>`));
59
+ console.log();
60
+
61
+ } catch (error) {
62
+ console.error(chalk.red.bold('\n✗ Failed to list backups:'), chalk.red(error.message));
63
+ process.exit(1);
64
+ }
65
+ }
66
+
67
+ module.exports = backupListCommand;
@@ -0,0 +1,73 @@
1
+ const chalk = require('chalk');
2
+ const readline = require('readline');
3
+ const api = require('../api');
4
+ const config = require('../config');
5
+
6
+ async function askConfirmation(question) {
7
+ const rl = readline.createInterface({
8
+ input: process.stdin,
9
+ output: process.stdout,
10
+ });
11
+
12
+ return new Promise((resolve) => {
13
+ rl.question(question, (answer) => {
14
+ rl.close();
15
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
16
+ });
17
+ });
18
+ }
19
+
20
+ async function backupRestoreCommand(databaseId, backupId) {
21
+ console.log(chalk.blue.bold('\n🔄 Restore Backup\n'));
22
+
23
+ try {
24
+ // Check if logged in
25
+ if (!config.isLoggedIn()) {
26
+ console.error(chalk.red.bold('✗ Not logged in'));
27
+ console.log(chalk.gray('\nPlease run'), chalk.cyan('vibedb login'), chalk.gray('or'), chalk.cyan('vibedb signup'), chalk.gray('first.'));
28
+ process.exit(1);
29
+ }
30
+
31
+ if (!databaseId || !backupId) {
32
+ console.error(chalk.red.bold('✗ Database ID and Backup ID are required'));
33
+ console.log(chalk.gray('\nUsage:'), chalk.cyan('vibedb backup restore <database-id> <backup-id>'));
34
+ process.exit(1);
35
+ }
36
+
37
+ const apiKey = config.getAPIKey();
38
+
39
+ // Warning message
40
+ console.log(chalk.yellow.bold('⚠️ WARNING:'));
41
+ console.log(chalk.yellow('This will replace ALL current data in the database with the backup.'));
42
+ console.log(chalk.yellow('This action cannot be undone.\n'));
43
+
44
+ console.log(chalk.gray('Database ID:'), chalk.cyan(databaseId));
45
+ console.log(chalk.gray('Backup ID:'), chalk.cyan(backupId));
46
+ console.log();
47
+
48
+ const confirmed = await askConfirmation(chalk.white('Are you sure you want to restore this backup? (y/N): '));
49
+
50
+ if (!confirmed) {
51
+ console.log(chalk.gray('\nRestore cancelled.'));
52
+ return;
53
+ }
54
+
55
+ console.log(chalk.gray('\nRestoring backup...'));
56
+
57
+ const result = await api.restoreBackup(apiKey, databaseId, backupId);
58
+
59
+ console.log(chalk.green.bold('\n✓ Backup restored successfully!\n'));
60
+ console.log(chalk.gray('Database ID:'), chalk.cyan(databaseId));
61
+ console.log(chalk.gray('Backup ID:'), chalk.cyan(backupId));
62
+ console.log(chalk.gray('Restored at:'), chalk.white(new Date().toLocaleString()));
63
+ console.log();
64
+ console.log(chalk.green('Your database has been restored to the backup state.'));
65
+ console.log();
66
+
67
+ } catch (error) {
68
+ console.error(chalk.red.bold('\n✗ Failed to restore backup:'), chalk.red(error.message));
69
+ process.exit(1);
70
+ }
71
+ }
72
+
73
+ module.exports = backupRestoreCommand;
@@ -15,10 +15,11 @@ async function listCommand() {
15
15
  }
16
16
 
17
17
  const apiKey = config.getAPIKey();
18
+ const currentTeamId = config.getCurrentTeamId();
18
19
 
19
20
  console.log(chalk.gray('Fetching databases...'));
20
21
 
21
- const databases = await api.listDatabases(apiKey);
22
+ const databases = await api.listDatabases(apiKey, currentTeamId);
22
23
 
23
24
  if (databases.length === 0) {
24
25
  console.log(chalk.yellow('\nNo databases found.'));
@@ -54,6 +54,21 @@ async function loginWithGitHub() {
54
54
  if (pollData.status === 'complete') {
55
55
  // Save API key
56
56
  config.saveAuth(pollData.api_key, pollData.email);
57
+
58
+ // Auto-select first team (usually personal team)
59
+ try {
60
+ const teams = await api.listTeams(pollData.api_key);
61
+ if (teams.length > 0) {
62
+ // Prefer personal team, otherwise first team
63
+ const personalTeam = teams.find(t => t.is_personal);
64
+ const defaultTeam = personalTeam || teams[0];
65
+ config.setCurrentTeamId(defaultTeam.id);
66
+ }
67
+ } catch (err) {
68
+ // Non-fatal, just log
69
+ console.log(chalk.gray('Note: Could not auto-select team'));
70
+ }
71
+
57
72
  console.log(chalk.green.bold('\n✓ Login successful!\n'));
58
73
  console.log(chalk.white('Email:'), chalk.cyan(pollData.email));
59
74
  console.log(chalk.gray(`API key saved to ${config.getConfigPath()}`));
@@ -116,6 +131,20 @@ async function loginWithEmail() {
116
131
  // Save API key to config
117
132
  config.saveAuth(result.api_key, result.email);
118
133
 
134
+ // Auto-select first team (usually personal team)
135
+ try {
136
+ const teams = await api.listTeams(result.api_key);
137
+ if (teams.length > 0) {
138
+ // Prefer personal team, otherwise first team
139
+ const personalTeam = teams.find(t => t.is_personal);
140
+ const defaultTeam = personalTeam || teams[0];
141
+ config.setCurrentTeamId(defaultTeam.id);
142
+ }
143
+ } catch (err) {
144
+ // Non-fatal, just log
145
+ console.log(chalk.gray('Note: Could not auto-select team'));
146
+ }
147
+
119
148
  console.log(chalk.green.bold('\n✓ Login successful!\n'));
120
149
  console.log(chalk.white('Email:'), chalk.cyan(result.email));
121
150
  console.log(chalk.gray(`API key saved to ${config.getConfigPath()}`));
@@ -50,6 +50,19 @@ async function signupWithGitHub() {
50
50
 
51
51
  if (pollData.status === 'complete') {
52
52
  config.saveAuth(pollData.api_key, pollData.email);
53
+
54
+ // Auto-select first team (usually personal team)
55
+ try {
56
+ const teams = await api.listTeams(pollData.api_key);
57
+ if (teams.length > 0) {
58
+ const personalTeam = teams.find(t => t.is_personal);
59
+ const defaultTeam = personalTeam || teams[0];
60
+ config.setCurrentTeamId(defaultTeam.id);
61
+ }
62
+ } catch (err) {
63
+ // Non-fatal
64
+ }
65
+
53
66
  console.log(chalk.green.bold('\n✓ Account created successfully!\n'));
54
67
  console.log(chalk.white('Email:'), chalk.cyan(pollData.email));
55
68
  console.log(chalk.gray(`API key saved to ${config.getConfigPath()}`));
@@ -128,6 +141,18 @@ async function signupWithEmail() {
128
141
 
129
142
  config.saveAuth(result.api_key, result.email);
130
143
 
144
+ // Auto-select first team (usually personal team)
145
+ try {
146
+ const teams = await api.listTeams(result.api_key);
147
+ if (teams.length > 0) {
148
+ const personalTeam = teams.find(t => t.is_personal);
149
+ const defaultTeam = personalTeam || teams[0];
150
+ config.setCurrentTeamId(defaultTeam.id);
151
+ }
152
+ } catch (err) {
153
+ // Non-fatal
154
+ }
155
+
131
156
  console.log(chalk.green.bold('\n✓ Account created successfully!\n'));
132
157
  console.log(chalk.white('Email:'), chalk.cyan(result.email));
133
158
  console.log(chalk.gray(`API key saved to ${config.getConfigPath()}`));
@@ -0,0 +1,42 @@
1
+ const chalk = require('chalk');
2
+ const api = require('../api');
3
+ const config = require('../config');
4
+
5
+ async function teamsCurrentCommand() {
6
+ try {
7
+ // Check if logged in
8
+ if (!config.isLoggedIn()) {
9
+ console.error(chalk.red.bold('✗ Not logged in'));
10
+ console.log(chalk.gray('\nPlease run'), chalk.cyan('vibedb login'), chalk.gray('or'), chalk.cyan('vibedb signup'), chalk.gray('first.'));
11
+ process.exit(1);
12
+ }
13
+
14
+ const apiKey = config.getAPIKey();
15
+ const currentTeamId = config.getCurrentTeamId();
16
+
17
+ if (!currentTeamId) {
18
+ console.log(chalk.yellow('\nNo team currently selected.'));
19
+ console.log(chalk.gray('\nSwitch to a team with:'), chalk.cyan('vibedb teams switch <team_id>'));
20
+ console.log(chalk.gray('List your teams with:'), chalk.cyan('vibedb teams list'));
21
+ console.log();
22
+ return;
23
+ }
24
+
25
+ console.log(chalk.gray('Fetching current team...'));
26
+
27
+ const team = await api.getTeam(apiKey, currentTeamId);
28
+
29
+ console.log(chalk.blue.bold('\n📍 Current Team\n'));
30
+ console.log(chalk.gray('ID:'), chalk.cyan(team.id));
31
+ console.log(chalk.gray('Name:'), chalk.white(team.name));
32
+ console.log(chalk.gray('Role:'), chalk.blue(team.role));
33
+ console.log(chalk.gray('Type:'), team.is_personal ? chalk.gray('Personal') : chalk.white('Team'));
34
+ console.log();
35
+ } catch (error) {
36
+ console.error(chalk.red.bold('\n✗ Failed to get current team:'), chalk.red(error.message));
37
+ console.log(chalk.gray('\nThe team may have been deleted. Switch to a different team with:'), chalk.cyan('vibedb teams switch <team_id>'));
38
+ process.exit(1);
39
+ }
40
+ }
41
+
42
+ module.exports = teamsCurrentCommand;
@@ -0,0 +1,84 @@
1
+ const chalk = require('chalk');
2
+ const Table = require('cli-table3');
3
+ const api = require('../api');
4
+ const config = require('../config');
5
+
6
+ async function teamsListCommand() {
7
+ console.log(chalk.blue.bold('\n👥 Your Teams\n'));
8
+
9
+ try {
10
+ // Check if logged in
11
+ if (!config.isLoggedIn()) {
12
+ console.error(chalk.red.bold('✗ Not logged in'));
13
+ console.log(chalk.gray('\nPlease run'), chalk.cyan('vibedb login'), chalk.gray('or'), chalk.cyan('vibedb signup'), chalk.gray('first.'));
14
+ process.exit(1);
15
+ }
16
+
17
+ const apiKey = config.getAPIKey();
18
+ const currentTeamId = config.getCurrentTeamId();
19
+
20
+ console.log(chalk.gray('Fetching teams...'));
21
+
22
+ const teams = await api.listTeams(apiKey);
23
+
24
+ if (teams.length === 0) {
25
+ console.log(chalk.yellow('\nNo teams found.'));
26
+ console.log();
27
+ return;
28
+ }
29
+
30
+ // Create table
31
+ const table = new Table({
32
+ head: [
33
+ chalk.white.bold('ID'),
34
+ chalk.white.bold('Name'),
35
+ chalk.white.bold('Role'),
36
+ chalk.white.bold('Type'),
37
+ chalk.white.bold('Current'),
38
+ ],
39
+ colWidths: [30, 25, 12, 12, 10],
40
+ style: {
41
+ head: [],
42
+ border: ['gray'],
43
+ },
44
+ });
45
+
46
+ teams.forEach((team) => {
47
+ const isCurrent = team.id === currentTeamId;
48
+ const currentMark = isCurrent ? chalk.green('✓') : '';
49
+ const teamName = team.is_personal
50
+ ? chalk.gray(`${team.name} (Personal)`)
51
+ : team.name;
52
+
53
+ table.push([
54
+ chalk.cyan(team.id),
55
+ teamName,
56
+ chalk.blue(team.role),
57
+ team.is_personal ? chalk.gray('Personal') : chalk.white('Team'),
58
+ currentMark,
59
+ ]);
60
+ });
61
+
62
+ console.log();
63
+ console.log(table.toString());
64
+ console.log();
65
+
66
+ if (currentTeamId) {
67
+ const currentTeam = teams.find(t => t.id === currentTeamId);
68
+ if (currentTeam) {
69
+ console.log(chalk.gray(`Current team: ${currentTeam.name}`));
70
+ }
71
+ } else {
72
+ console.log(chalk.yellow('No team selected. Use'), chalk.cyan('vibedb teams switch <team_id>'), chalk.yellow('to select a team.'));
73
+ }
74
+
75
+ console.log();
76
+ console.log(chalk.gray(`Total: ${teams.length} team${teams.length === 1 ? '' : 's'}`));
77
+ console.log();
78
+ } catch (error) {
79
+ console.error(chalk.red.bold('\n✗ Failed to list teams:'), chalk.red(error.message));
80
+ process.exit(1);
81
+ }
82
+ }
83
+
84
+ module.exports = teamsListCommand;
@@ -0,0 +1,41 @@
1
+ const chalk = require('chalk');
2
+ const api = require('../api');
3
+ const config = require('../config');
4
+
5
+ async function teamsSwitchCommand(teamId) {
6
+ try {
7
+ // Check if logged in
8
+ if (!config.isLoggedIn()) {
9
+ console.error(chalk.red.bold('✗ Not logged in'));
10
+ console.log(chalk.gray('\nPlease run'), chalk.cyan('vibedb login'), chalk.gray('or'), chalk.cyan('vibedb signup'), chalk.gray('first.'));
11
+ process.exit(1);
12
+ }
13
+
14
+ if (!teamId) {
15
+ console.error(chalk.red.bold('✗ Team ID required'));
16
+ console.log(chalk.gray('\nUsage:'), chalk.cyan('vibedb teams switch <team_id>'));
17
+ console.log(chalk.gray('\nList your teams with:'), chalk.cyan('vibedb teams list'));
18
+ process.exit(1);
19
+ }
20
+
21
+ const apiKey = config.getAPIKey();
22
+
23
+ console.log(chalk.gray(`Switching to team ${teamId}...`));
24
+
25
+ // Verify team exists and user has access
26
+ const team = await api.getTeam(apiKey, teamId);
27
+
28
+ // Save as current team
29
+ config.setCurrentTeamId(teamId);
30
+
31
+ console.log(chalk.green.bold('\n✓ Successfully switched to team'));
32
+ console.log(chalk.gray('Team:'), chalk.white(team.name));
33
+ console.log(chalk.gray('Role:'), chalk.blue(team.role));
34
+ console.log();
35
+ } catch (error) {
36
+ console.error(chalk.red.bold('\n✗ Failed to switch team:'), chalk.red(error.message));
37
+ process.exit(1);
38
+ }
39
+ }
40
+
41
+ module.exports = teamsSwitchCommand;
@@ -0,0 +1,66 @@
1
+ const chalk = require('chalk');
2
+ const api = require('../api');
3
+ const config = require('../config');
4
+
5
+ async function usageCommand() {
6
+ console.log(chalk.blue.bold('\n📊 Usage & Costs\n'));
7
+
8
+ try {
9
+ // Check if logged in
10
+ if (!config.isLoggedIn()) {
11
+ console.error(chalk.red.bold('✗ Not logged in'));
12
+ console.log(chalk.gray('\nPlease run'), chalk.cyan('vibedb login'), chalk.gray('or'), chalk.cyan('vibedb signup'), chalk.gray('first.'));
13
+ process.exit(1);
14
+ }
15
+
16
+ const apiKey = config.getAPIKey();
17
+ const currentTeamId = config.getCurrentTeamId();
18
+
19
+ console.log(chalk.gray('Fetching usage data...'));
20
+
21
+ const account = await api.getAccount(apiKey, currentTeamId);
22
+
23
+ console.log();
24
+
25
+ // Storage breakdown
26
+ console.log(chalk.white.bold('Storage Usage:'));
27
+
28
+ if (account.storage_breakdown) {
29
+ const { database_storage_mb, backup_storage_mb, total_storage_mb } = account.storage_breakdown;
30
+
31
+ console.log(chalk.gray(' Database Storage:'), chalk.white(`${database_storage_mb.toFixed(2)} MB`));
32
+ console.log(chalk.gray(' Backup Storage:'), chalk.white(`${backup_storage_mb.toFixed(2)} MB`));
33
+ console.log(chalk.gray(' ─────────────────'));
34
+ console.log(chalk.gray(' Total Storage:'), chalk.cyan.bold(`${total_storage_mb.toFixed(2)} MB`));
35
+ } else {
36
+ console.log(chalk.gray(' Total Storage:'), chalk.cyan.bold(`${account.storage_total_mb.toFixed(2)} MB`));
37
+ }
38
+
39
+ console.log();
40
+
41
+ // Cost information
42
+ console.log(chalk.white.bold('Estimated Charges:'));
43
+ const charges = account.estimated_charges_usd || 0;
44
+ console.log(chalk.gray(' Current Period:'), chalk.green.bold(`$${charges.toFixed(4)} USD`));
45
+ console.log();
46
+
47
+ // Pricing info
48
+ console.log(chalk.gray('Pricing:'));
49
+ console.log(chalk.gray(' Storage: $0.00014 per GB-hour'));
50
+ console.log(chalk.gray(' Compute: $0.000167 per minute'));
51
+ console.log();
52
+
53
+ // Show current team if applicable
54
+ if (currentTeamId) {
55
+ console.log(chalk.gray('Team:'), chalk.cyan(currentTeamId));
56
+ console.log(chalk.gray('Run'), chalk.cyan('vibedb teams list'), chalk.gray('to see all your teams.'));
57
+ console.log();
58
+ }
59
+
60
+ } catch (error) {
61
+ console.error(chalk.red.bold('\n✗ Failed to fetch usage:'), chalk.red(error.message));
62
+ process.exit(1);
63
+ }
64
+ }
65
+
66
+ module.exports = usageCommand;
package/src/config.js CHANGED
@@ -68,6 +68,23 @@ function saveAuth(apiKey, email) {
68
68
  writeConfig(config);
69
69
  }
70
70
 
71
+ /**
72
+ * Get current team ID from config
73
+ */
74
+ function getCurrentTeamId() {
75
+ const config = readConfig();
76
+ return config?.current_team_id || null;
77
+ }
78
+
79
+ /**
80
+ * Set current team ID in config
81
+ */
82
+ function setCurrentTeamId(teamId) {
83
+ const config = readConfig() || {};
84
+ config.current_team_id = teamId;
85
+ writeConfig(config);
86
+ }
87
+
71
88
  module.exports = {
72
89
  getConfigPath,
73
90
  readConfig,
@@ -75,4 +92,6 @@ module.exports = {
75
92
  isLoggedIn,
76
93
  getAPIKey,
77
94
  saveAuth,
95
+ getCurrentTeamId,
96
+ setCurrentTeamId,
78
97
  };