@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 +63 -0
- package/README.md +225 -2
- package/bin/vibedb.js +116 -0
- package/package.json +1 -1
- package/src/api.js +149 -4
- package/src/commands/backup-create.js +48 -0
- package/src/commands/backup-delete.js +64 -0
- package/src/commands/backup-list.js +67 -0
- package/src/commands/backup-restore.js +73 -0
- package/src/commands/list.js +2 -1
- package/src/commands/login.js +29 -0
- package/src/commands/signup.js +25 -0
- package/src/commands/teams-current.js +42 -0
- package/src/commands/teams-list.js +84 -0
- package/src/commands/teams-switch.js +41 -0
- package/src/commands/usage.js +66 -0
- package/src/config.js +19 -0
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
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
|
|
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
|
|
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;
|
package/src/commands/list.js
CHANGED
|
@@ -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.'));
|
package/src/commands/login.js
CHANGED
|
@@ -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()}`));
|
package/src/commands/signup.js
CHANGED
|
@@ -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
|
};
|