@vibe-db/cli 1.2.1 → 1.3.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/README.md CHANGED
@@ -99,6 +99,42 @@ 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)
102
138
 
103
139
  ## Configuration
104
140
 
@@ -108,10 +144,13 @@ The CLI stores your credentials in `~/.vibedb`:
108
144
  {
109
145
  "api_key": "vdb_xxx...",
110
146
  "email": "you@example.com",
111
- "api_url": "https://api.vibedb.dev"
147
+ "api_url": "https://api.vibedb.dev",
148
+ "current_team_id": "team_abc123"
112
149
  }
113
150
  ```
114
151
 
152
+ **Team Context:** The CLI remembers your current team selection. Use `vibedb teams switch` to change teams.
153
+
115
154
  **Security Note:** Keep this file secure. Anyone with your API key can manage your databases.
116
155
 
117
156
  ## Examples
@@ -150,6 +189,50 @@ $ npx @vibedb/cli list
150
189
  Total: 1 database
151
190
  ```
152
191
 
192
+ ### Working with Teams
193
+ ```bash
194
+ # List all your teams
195
+ $ vibedb teams list
196
+
197
+ ┌──────────────────┬──────────────┬────────┬──────────┬─────────┐
198
+ │ ID │ Name │ Role │ Type │ Current │
199
+ ├──────────────────┼──────────────┼────────┼──────────┼─────────┤
200
+ │ team_abc123 │ John's Team │ owner │ Personal │ ✓ │
201
+ │ team_xyz789 │ Acme Corp │ member │ Team │ │
202
+ └──────────────────┴──────────────┴────────┴──────────┴─────────┘
203
+
204
+ Total: 2 teams
205
+
206
+ # Switch to a different team
207
+ $ vibedb teams switch team_xyz789
208
+
209
+ ✓ Successfully switched to team
210
+ Team: Acme Corp
211
+ Role: member
212
+
213
+ # List databases in the new team context
214
+ $ vibedb list
215
+
216
+ ┌─────────────────┬────────────────┬──────────┬────────┐
217
+ │ ID │ Name │ Type │ Status │
218
+ ├─────────────────┼────────────────┼──────────┼────────┤
219
+ │ db_def456 │ production-db │ postgres │ ready │
220
+ │ db_ghi789 │ staging-cache │ redis │ ready │
221
+ └─────────────────┴────────────────┴──────────┴────────┘
222
+
223
+ Total: 2 databases
224
+
225
+ # Check current team
226
+ $ vibedb teams current
227
+
228
+ 📍 Current Team
229
+
230
+ ID: team_xyz789
231
+ Name: Acme Corp
232
+ Role: member
233
+ Type: Team
234
+ ```
235
+
153
236
  ## Supported Databases
154
237
 
155
238
  - **PostgreSQL** - General purpose relational database
package/bin/vibedb.js CHANGED
@@ -12,6 +12,9 @@ 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');
15
18
 
16
19
  const program = new Command();
17
20
 
@@ -134,6 +137,48 @@ billing
134
137
  }
135
138
  });
136
139
 
140
+ // Teams commands
141
+ const teams = program
142
+ .command('teams')
143
+ .description('Manage teams and team membership');
144
+
145
+ teams
146
+ .command('list')
147
+ .alias('ls')
148
+ .description('List all teams you belong to')
149
+ .action(async () => {
150
+ try {
151
+ await teamsListCommand();
152
+ } catch (error) {
153
+ console.error(chalk.red('Unexpected error:'), error.message);
154
+ process.exit(1);
155
+ }
156
+ });
157
+
158
+ teams
159
+ .command('switch <team_id>')
160
+ .description('Switch to a different team')
161
+ .action(async (teamId) => {
162
+ try {
163
+ await teamsSwitchCommand(teamId);
164
+ } catch (error) {
165
+ console.error(chalk.red('Unexpected error:'), error.message);
166
+ process.exit(1);
167
+ }
168
+ });
169
+
170
+ teams
171
+ .command('current')
172
+ .description('Show current team')
173
+ .action(async () => {
174
+ try {
175
+ await teamsCurrentCommand();
176
+ } catch (error) {
177
+ console.error(chalk.red('Unexpected error:'), error.message);
178
+ process.exit(1);
179
+ }
180
+ });
181
+
137
182
  // Global error handler
138
183
  process.on('unhandledRejection', (error) => {
139
184
  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.1",
3
+ "version": "1.3.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,46 @@ 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
+
218
266
  module.exports = {
219
267
  signup,
220
268
  login,
@@ -227,4 +275,6 @@ module.exports = {
227
275
  getInvoices,
228
276
  startDeviceAuth,
229
277
  pollDeviceAuth,
278
+ listTeams,
279
+ getTeam,
230
280
  };
@@ -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()}`));
@@ -18,15 +18,19 @@ async function signupWithGitHub() {
18
18
  const data = await api.startDeviceAuth();
19
19
  const { device_code, user_code, verification_uri, interval } = data;
20
20
 
21
+ // Build URL with pre-populated code for better UX
22
+ const urlWithCode = `${verification_uri}?code=${formatUserCode(user_code)}`;
23
+
21
24
  console.log(chalk.bold('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
22
- console.log(chalk.cyan.bold(` Open: ${verification_uri}`));
23
- console.log(chalk.yellow.bold(` Enter code: ${formatUserCode(user_code)}`));
25
+ console.log(chalk.cyan.bold(` Open: ${urlWithCode}`));
26
+ console.log(chalk.yellow.bold(` Your code: ${formatUserCode(user_code)}`));
24
27
  console.log(chalk.bold('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
25
28
 
29
+ // Try to open browser automatically with pre-populated code
26
30
  try {
27
31
  const open = (await import('open')).default;
28
- await open(verification_uri);
29
- console.log(chalk.gray('Opened browser automatically...\n'));
32
+ await open(urlWithCode);
33
+ console.log(chalk.gray('Opened browser with code pre-filled...\n'));
30
34
  } catch (err) {
31
35
  console.log(chalk.gray('Please open the URL manually\n'));
32
36
  }
@@ -46,6 +50,19 @@ async function signupWithGitHub() {
46
50
 
47
51
  if (pollData.status === 'complete') {
48
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
+
49
66
  console.log(chalk.green.bold('\n✓ Account created successfully!\n'));
50
67
  console.log(chalk.white('Email:'), chalk.cyan(pollData.email));
51
68
  console.log(chalk.gray(`API key saved to ${config.getConfigPath()}`));
@@ -124,6 +141,18 @@ async function signupWithEmail() {
124
141
 
125
142
  config.saveAuth(result.api_key, result.email);
126
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
+
127
156
  console.log(chalk.green.bold('\n✓ Account created successfully!\n'));
128
157
  console.log(chalk.white('Email:'), chalk.cyan(result.email));
129
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;
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
  };