genbox 1.0.190 → 1.0.192
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/dist/commands/create.js +35 -0
- package/dist/commands/init.js +26 -1
- package/dist/commands/org.js +409 -0
- package/dist/commands/setup.js +25 -2
- package/dist/index.js +2 -0
- package/dist/utils/gemini-auth.js +127 -0
- package/dist/utils/index.js +1 -0
- package/package.json +1 -1
package/dist/commands/create.js
CHANGED
|
@@ -654,6 +654,7 @@ exports.createCommand = new commander_1.Command('create')
|
|
|
654
654
|
.option('--dry-run', 'Show what would be created without actually creating')
|
|
655
655
|
.option('-r, --restore', 'Restore from backup (uses genbox name to find backup)')
|
|
656
656
|
.option('--inject-claude-auth', 'Inject local Claude Code credentials for remote execution')
|
|
657
|
+
.option('--inject-gemini-auth', 'Inject local Gemini CLI credentials for remote execution')
|
|
657
658
|
.action(async (nameArg, options) => {
|
|
658
659
|
try {
|
|
659
660
|
// Handle local genbox creation
|
|
@@ -1142,6 +1143,40 @@ exports.createCommand = new commander_1.Command('create')
|
|
|
1142
1143
|
console.log(chalk_1.default.dim(' Claude Code will be installed with your credentials'));
|
|
1143
1144
|
}
|
|
1144
1145
|
}
|
|
1146
|
+
// Handle Gemini CLI credential injection
|
|
1147
|
+
// Check both CLI flag and genbox.yaml config
|
|
1148
|
+
const shouldInjectGeminiAuth = options.injectGeminiAuth || config.defaults?.inject_gemini_auth;
|
|
1149
|
+
if (shouldInjectGeminiAuth) {
|
|
1150
|
+
console.log('');
|
|
1151
|
+
console.log(chalk_1.default.blue('=== Gemini CLI Authentication ==='));
|
|
1152
|
+
const geminiCreds = (0, utils_1.getGeminiCredentials)();
|
|
1153
|
+
if (!geminiCreds.found) {
|
|
1154
|
+
console.log(chalk_1.default.yellow('⚠ Gemini CLI credentials not found'));
|
|
1155
|
+
console.log(chalk_1.default.dim(` ${geminiCreds.error}`));
|
|
1156
|
+
console.log('');
|
|
1157
|
+
console.log((0, utils_1.getGeminiAuthInstructions)());
|
|
1158
|
+
console.log('');
|
|
1159
|
+
if (!options.yes) {
|
|
1160
|
+
const continueWithoutGemini = await prompts.confirm({
|
|
1161
|
+
message: 'Continue without Gemini authentication?',
|
|
1162
|
+
default: false,
|
|
1163
|
+
});
|
|
1164
|
+
if (!continueWithoutGemini) {
|
|
1165
|
+
console.log(chalk_1.default.dim('Cancelled.'));
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
else {
|
|
1170
|
+
console.log(chalk_1.default.dim('Continuing without Gemini authentication (use -y to skip prompts)'));
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
else {
|
|
1174
|
+
payload.geminiCredentials = geminiCreds.credentials;
|
|
1175
|
+
payload.installGeminiCli = true; // Auto-enable Gemini CLI installation
|
|
1176
|
+
console.log(chalk_1.default.green(`✓ Gemini credentials found (${geminiCreds.source})`));
|
|
1177
|
+
console.log(chalk_1.default.dim(' Gemini CLI will be installed with your credentials'));
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1145
1180
|
// Create genbox
|
|
1146
1181
|
const spinner = (0, ora_1.default)(`Creating Genbox '${name}'...`).start();
|
|
1147
1182
|
try {
|
package/dist/commands/init.js
CHANGED
|
@@ -833,7 +833,24 @@ async function getProjectSettings(detected, existingEnvValues) {
|
|
|
833
833
|
console.log(chalk_1.default.dim(' This enables running Claude prompts remotely with your subscription.'));
|
|
834
834
|
}
|
|
835
835
|
}
|
|
836
|
-
|
|
836
|
+
// Gemini CLI installation
|
|
837
|
+
const installGeminiCli = await prompts.confirm({
|
|
838
|
+
message: 'Install Gemini CLI on genbox servers?',
|
|
839
|
+
default: false,
|
|
840
|
+
});
|
|
841
|
+
// Gemini CLI credential injection
|
|
842
|
+
let injectGeminiAuth = false;
|
|
843
|
+
if (installGeminiCli) {
|
|
844
|
+
injectGeminiAuth = await prompts.confirm({
|
|
845
|
+
message: 'Inject your Gemini credentials into genboxes for remote execution?',
|
|
846
|
+
default: true,
|
|
847
|
+
});
|
|
848
|
+
if (injectGeminiAuth) {
|
|
849
|
+
console.log(chalk_1.default.dim(' Your local Gemini credentials will be injected when creating genboxes.'));
|
|
850
|
+
console.log(chalk_1.default.dim(' This enables running Gemini prompts remotely with your authentication.'));
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
return { projectName, serverSize, baseBranch, installClaudeCode, injectClaudeAuth, installGeminiCli, injectGeminiAuth };
|
|
837
854
|
}
|
|
838
855
|
// =============================================================================
|
|
839
856
|
// Git Auth Setup
|
|
@@ -1489,6 +1506,12 @@ function generateConfig(detected, settings, repos, environments, profiles) {
|
|
|
1489
1506
|
if (settings.injectClaudeAuth) {
|
|
1490
1507
|
defaults.inject_claude_auth = true;
|
|
1491
1508
|
}
|
|
1509
|
+
if (settings.installGeminiCli) {
|
|
1510
|
+
defaults.install_gemini_cli = true;
|
|
1511
|
+
}
|
|
1512
|
+
if (settings.injectGeminiAuth) {
|
|
1513
|
+
defaults.inject_gemini_auth = true;
|
|
1514
|
+
}
|
|
1492
1515
|
// Map structure type
|
|
1493
1516
|
const structureMap = {
|
|
1494
1517
|
'single-app': 'single-app',
|
|
@@ -2255,6 +2278,8 @@ exports.initCommand = new commander_1.Command('init')
|
|
|
2255
2278
|
baseBranch: detected.git?.branch || 'main',
|
|
2256
2279
|
installClaudeCode: true,
|
|
2257
2280
|
injectClaudeAuth: true,
|
|
2281
|
+
installGeminiCli: false,
|
|
2282
|
+
injectGeminiAuth: false,
|
|
2258
2283
|
};
|
|
2259
2284
|
const { repos, envVars: gitEnvVars } = await setupGitAuth(detected, settings.projectName, existingEnvValues);
|
|
2260
2285
|
const environments = {};
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.orgCommand = void 0;
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
10
|
+
const api_1 = require("../api");
|
|
11
|
+
exports.orgCommand = new commander_1.Command('org')
|
|
12
|
+
.description('Manage organizations')
|
|
13
|
+
.action(async () => {
|
|
14
|
+
// Default action: show current context and list organizations
|
|
15
|
+
try {
|
|
16
|
+
// Get current context
|
|
17
|
+
const context = await (0, api_1.fetchApi)('/users/me/context');
|
|
18
|
+
console.log(chalk_1.default.bold('\nCurrent Context'));
|
|
19
|
+
if (context.type === 'organization' && context.organizationUsername) {
|
|
20
|
+
console.log(` Working in: ${chalk_1.default.cyan(context.organizationUsername)} ${chalk_1.default.dim('(organization)')}`);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
console.log(` Working in: ${chalk_1.default.cyan('personal')} ${chalk_1.default.dim('(your account)')}`);
|
|
24
|
+
}
|
|
25
|
+
// List organizations
|
|
26
|
+
const orgs = await (0, api_1.fetchApi)('/organizations');
|
|
27
|
+
console.log(chalk_1.default.bold('\nYour Organizations'));
|
|
28
|
+
if (orgs.length === 0) {
|
|
29
|
+
console.log(chalk_1.default.dim(' No organizations yet.'));
|
|
30
|
+
console.log(chalk_1.default.dim('\n Create one with: gb org create <name>'));
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
for (const org of orgs) {
|
|
34
|
+
const roleColor = org.role === 'owner'
|
|
35
|
+
? chalk_1.default.yellow
|
|
36
|
+
: org.role === 'admin'
|
|
37
|
+
? chalk_1.default.blue
|
|
38
|
+
: chalk_1.default.dim;
|
|
39
|
+
console.log(` ${chalk_1.default.cyan(org.username)} - ${org.displayName} ${roleColor(`[${org.role}]`)}`);
|
|
40
|
+
console.log(chalk_1.default.dim(` ${org.memberCount} members`));
|
|
41
|
+
}
|
|
42
|
+
console.log(chalk_1.default.dim('\n Switch context: gb org switch <username>'));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if (error instanceof api_1.AuthenticationError) {
|
|
47
|
+
(0, api_1.handleApiError)(error);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
console.error(chalk_1.default.red('Error:'), error.message);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
// Sub-command: list organizations
|
|
54
|
+
exports.orgCommand
|
|
55
|
+
.command('list')
|
|
56
|
+
.alias('ls')
|
|
57
|
+
.description('List organizations you are a member of')
|
|
58
|
+
.action(async () => {
|
|
59
|
+
try {
|
|
60
|
+
const orgs = await (0, api_1.fetchApi)('/organizations');
|
|
61
|
+
if (orgs.length === 0) {
|
|
62
|
+
console.log(chalk_1.default.dim('No organizations found.'));
|
|
63
|
+
console.log(chalk_1.default.dim('\nCreate one with: gb org create <name>'));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
console.log(chalk_1.default.bold('\nOrganizations'));
|
|
67
|
+
for (const org of orgs) {
|
|
68
|
+
const roleColor = org.role === 'owner'
|
|
69
|
+
? chalk_1.default.yellow
|
|
70
|
+
: org.role === 'admin'
|
|
71
|
+
? chalk_1.default.blue
|
|
72
|
+
: chalk_1.default.dim;
|
|
73
|
+
console.log(`\n ${chalk_1.default.cyan(org.username)}`);
|
|
74
|
+
console.log(` Display name: ${org.displayName}`);
|
|
75
|
+
console.log(` Role: ${roleColor(org.role)}`);
|
|
76
|
+
console.log(` Members: ${org.memberCount}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
if (error instanceof api_1.AuthenticationError) {
|
|
81
|
+
(0, api_1.handleApiError)(error);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
console.error(chalk_1.default.red('Error:'), error.message);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
// Sub-command: create organization
|
|
88
|
+
exports.orgCommand
|
|
89
|
+
.command('create <username>')
|
|
90
|
+
.description('Create a new organization')
|
|
91
|
+
.option('-n, --name <displayName>', 'Display name for the organization')
|
|
92
|
+
.option('-d, --description <desc>', 'Organization description')
|
|
93
|
+
.action(async (username, options) => {
|
|
94
|
+
try {
|
|
95
|
+
const normalized = username.toLowerCase().trim();
|
|
96
|
+
// Validate format
|
|
97
|
+
if (normalized.length < 3) {
|
|
98
|
+
console.error(chalk_1.default.red('Organization username must be at least 3 characters'));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(normalized)) {
|
|
102
|
+
console.error(chalk_1.default.red('Username must be lowercase, alphanumeric with hyphens (no start/end hyphen)'));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
// Check availability
|
|
106
|
+
const check = await (0, api_1.fetchApi)(`/usernames/${normalized}/check`);
|
|
107
|
+
if (!check.available) {
|
|
108
|
+
console.error(chalk_1.default.red(`Username '${normalized}' is not available: ${check.reason}`));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
// Prompt for display name if not provided
|
|
112
|
+
let displayName = options.name;
|
|
113
|
+
if (!displayName) {
|
|
114
|
+
const answers = await inquirer_1.default.prompt([
|
|
115
|
+
{
|
|
116
|
+
type: 'input',
|
|
117
|
+
name: 'displayName',
|
|
118
|
+
message: 'Display name:',
|
|
119
|
+
default: normalized,
|
|
120
|
+
validate: (input) => input.trim().length > 0 || 'Display name is required',
|
|
121
|
+
},
|
|
122
|
+
]);
|
|
123
|
+
displayName = answers.displayName;
|
|
124
|
+
}
|
|
125
|
+
// Create organization
|
|
126
|
+
const result = await (0, api_1.fetchApi)('/organizations', {
|
|
127
|
+
method: 'POST',
|
|
128
|
+
body: JSON.stringify({
|
|
129
|
+
username: normalized,
|
|
130
|
+
displayName,
|
|
131
|
+
description: options.description,
|
|
132
|
+
}),
|
|
133
|
+
});
|
|
134
|
+
console.log(chalk_1.default.green(`\n✓ Organization '${result.username}' created!`));
|
|
135
|
+
console.log(chalk_1.default.dim(` Display name: ${result.displayName}`));
|
|
136
|
+
console.log(chalk_1.default.dim(`\n Switch to it: gb org switch ${result.username}`));
|
|
137
|
+
console.log(chalk_1.default.dim(` Invite members: gb org invite ${result.username} <email>`));
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
if (error.name === 'ExitPromptError' || error.message?.includes('force closed')) {
|
|
141
|
+
console.log(chalk_1.default.dim('Cancelled.'));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (error instanceof api_1.AuthenticationError) {
|
|
145
|
+
(0, api_1.handleApiError)(error);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
console.error(chalk_1.default.red('Error:'), error.message);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
// Sub-command: show organization info
|
|
152
|
+
exports.orgCommand
|
|
153
|
+
.command('info [username]')
|
|
154
|
+
.description('Show organization details')
|
|
155
|
+
.action(async (username) => {
|
|
156
|
+
try {
|
|
157
|
+
// If no username provided, use current context
|
|
158
|
+
if (!username) {
|
|
159
|
+
const context = await (0, api_1.fetchApi)('/users/me/context');
|
|
160
|
+
if (context.type !== 'organization' || !context.organizationUsername) {
|
|
161
|
+
console.error(chalk_1.default.red('Not in organization context. Specify username or switch context.'));
|
|
162
|
+
console.log(chalk_1.default.dim('\n gb org info <username>'));
|
|
163
|
+
console.log(chalk_1.default.dim(' gb org switch <username>'));
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
username = context.organizationUsername;
|
|
167
|
+
}
|
|
168
|
+
const org = await (0, api_1.fetchApi)(`/organizations/${username}`);
|
|
169
|
+
if (!org.id) {
|
|
170
|
+
// Non-member view
|
|
171
|
+
console.log(chalk_1.default.bold(`\nOrganization: ${chalk_1.default.cyan(org.username)}`));
|
|
172
|
+
console.log(` Display name: ${org.displayName}`);
|
|
173
|
+
if (org.description)
|
|
174
|
+
console.log(` Description: ${org.description}`);
|
|
175
|
+
console.log(chalk_1.default.dim('\n You are not a member of this organization.'));
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
// Member view
|
|
179
|
+
console.log(chalk_1.default.bold(`\nOrganization: ${chalk_1.default.cyan(org.username)}`));
|
|
180
|
+
console.log(` Display name: ${org.displayName}`);
|
|
181
|
+
if (org.description)
|
|
182
|
+
console.log(` Description: ${org.description}`);
|
|
183
|
+
console.log(` Your role: ${chalk_1.default.cyan(org.myRole)}`);
|
|
184
|
+
console.log(` Tier: ${org.tier || 'free'}`);
|
|
185
|
+
console.log(` Credits: ${org.credits || 0} + ${org.addonCredits || 0} addon`);
|
|
186
|
+
if (org.members && org.members.length > 0) {
|
|
187
|
+
console.log(chalk_1.default.bold('\n Members:'));
|
|
188
|
+
for (const member of org.members) {
|
|
189
|
+
const roleColor = member.role === 'owner'
|
|
190
|
+
? chalk_1.default.yellow
|
|
191
|
+
: member.role === 'admin'
|
|
192
|
+
? chalk_1.default.blue
|
|
193
|
+
: chalk_1.default.dim;
|
|
194
|
+
console.log(` ${member.email} ${roleColor(`[${member.role}]`)}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (org.pendingInvitations && org.pendingInvitations.length > 0) {
|
|
198
|
+
console.log(chalk_1.default.bold('\n Pending Invitations:'));
|
|
199
|
+
for (const inv of org.pendingInvitations) {
|
|
200
|
+
console.log(` ${inv.email} ${chalk_1.default.dim(`[${inv.role}]`)} - expires ${inv.expiresAt}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
if (error instanceof api_1.AuthenticationError) {
|
|
206
|
+
(0, api_1.handleApiError)(error);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
console.error(chalk_1.default.red('Error:'), error.message);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
// Sub-command: switch context
|
|
213
|
+
exports.orgCommand
|
|
214
|
+
.command('switch <username>')
|
|
215
|
+
.description('Switch to organization context (use "personal" for personal account)')
|
|
216
|
+
.action(async (username) => {
|
|
217
|
+
try {
|
|
218
|
+
if (username.toLowerCase() === 'personal' || username.toLowerCase() === 'me') {
|
|
219
|
+
// Switch to personal context
|
|
220
|
+
await (0, api_1.fetchApi)('/users/me/context', {
|
|
221
|
+
method: 'PUT',
|
|
222
|
+
body: JSON.stringify({ type: 'personal' }),
|
|
223
|
+
});
|
|
224
|
+
console.log(chalk_1.default.green('✓ Switched to personal context'));
|
|
225
|
+
console.log(chalk_1.default.dim(' New genboxes will be created under your personal account.'));
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
// Get organization details
|
|
229
|
+
const org = await (0, api_1.fetchApi)(`/organizations/${username}`);
|
|
230
|
+
if (!org.id) {
|
|
231
|
+
console.error(chalk_1.default.red(`You are not a member of organization '${username}'`));
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
// Switch to organization context
|
|
235
|
+
await (0, api_1.fetchApi)('/users/me/context', {
|
|
236
|
+
method: 'PUT',
|
|
237
|
+
body: JSON.stringify({
|
|
238
|
+
type: 'organization',
|
|
239
|
+
organizationId: org.id,
|
|
240
|
+
}),
|
|
241
|
+
});
|
|
242
|
+
console.log(chalk_1.default.green(`✓ Switched to organization: ${org.displayName}`));
|
|
243
|
+
console.log(chalk_1.default.dim(` New genboxes will be created under ${org.username}.`));
|
|
244
|
+
console.log(chalk_1.default.dim(` URLs: https://{genbox}.app.${org.username}.genbox.dev`));
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
if (error instanceof api_1.AuthenticationError) {
|
|
248
|
+
(0, api_1.handleApiError)(error);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
console.error(chalk_1.default.red('Error:'), error.message);
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
// Sub-command: invite member
|
|
255
|
+
exports.orgCommand
|
|
256
|
+
.command('invite <username> <email>')
|
|
257
|
+
.description('Invite a member to the organization')
|
|
258
|
+
.option('-r, --role <role>', 'Role for the new member (admin, member)', 'member')
|
|
259
|
+
.action(async (username, email, options) => {
|
|
260
|
+
try {
|
|
261
|
+
if (!['admin', 'member'].includes(options.role)) {
|
|
262
|
+
console.error(chalk_1.default.red('Role must be "admin" or "member"'));
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const result = await (0, api_1.fetchApi)(`/organizations/${username}/members`, {
|
|
266
|
+
method: 'POST',
|
|
267
|
+
body: JSON.stringify({ email, role: options.role }),
|
|
268
|
+
});
|
|
269
|
+
console.log(chalk_1.default.green(`✓ Invitation sent to ${email}`));
|
|
270
|
+
console.log(chalk_1.default.dim(` Role: ${result.role}`));
|
|
271
|
+
console.log(chalk_1.default.dim(` Expires: ${result.expiresAt}`));
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
if (error instanceof api_1.AuthenticationError) {
|
|
275
|
+
(0, api_1.handleApiError)(error);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
console.error(chalk_1.default.red('Error:'), error.message);
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
// Sub-command: list members
|
|
282
|
+
exports.orgCommand
|
|
283
|
+
.command('members [username]')
|
|
284
|
+
.description('List organization members')
|
|
285
|
+
.action(async (username) => {
|
|
286
|
+
try {
|
|
287
|
+
// If no username provided, use current context
|
|
288
|
+
if (!username) {
|
|
289
|
+
const context = await (0, api_1.fetchApi)('/users/me/context');
|
|
290
|
+
if (context.type !== 'organization' || !context.organizationUsername) {
|
|
291
|
+
console.error(chalk_1.default.red('Not in organization context. Specify username or switch context.'));
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
username = context.organizationUsername;
|
|
295
|
+
}
|
|
296
|
+
const result = await (0, api_1.fetchApi)(`/organizations/${username}/members`);
|
|
297
|
+
console.log(chalk_1.default.bold(`\nMembers of ${chalk_1.default.cyan(username)}`));
|
|
298
|
+
if (result.members && result.members.length > 0) {
|
|
299
|
+
for (const member of result.members) {
|
|
300
|
+
const roleColor = member.role === 'owner'
|
|
301
|
+
? chalk_1.default.yellow
|
|
302
|
+
: member.role === 'admin'
|
|
303
|
+
? chalk_1.default.blue
|
|
304
|
+
: chalk_1.default.dim;
|
|
305
|
+
console.log(` ${member.email} ${roleColor(`[${member.role}]`)}`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if (result.pendingInvitations && result.pendingInvitations.length > 0) {
|
|
309
|
+
console.log(chalk_1.default.bold('\nPending Invitations'));
|
|
310
|
+
for (const inv of result.pendingInvitations) {
|
|
311
|
+
console.log(` ${inv.email} ${chalk_1.default.dim(`[${inv.role}]`)} - pending`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
catch (error) {
|
|
316
|
+
if (error instanceof api_1.AuthenticationError) {
|
|
317
|
+
(0, api_1.handleApiError)(error);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
console.error(chalk_1.default.red('Error:'), error.message);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
// Sub-command: leave organization
|
|
324
|
+
exports.orgCommand
|
|
325
|
+
.command('leave <username>')
|
|
326
|
+
.description('Leave an organization')
|
|
327
|
+
.action(async (username) => {
|
|
328
|
+
try {
|
|
329
|
+
// Get current user info
|
|
330
|
+
const user = await (0, api_1.fetchApi)('/users/me');
|
|
331
|
+
// Confirm
|
|
332
|
+
const { confirm } = await inquirer_1.default.prompt([
|
|
333
|
+
{
|
|
334
|
+
type: 'confirm',
|
|
335
|
+
name: 'confirm',
|
|
336
|
+
message: `Are you sure you want to leave organization '${username}'?`,
|
|
337
|
+
default: false,
|
|
338
|
+
},
|
|
339
|
+
]);
|
|
340
|
+
if (!confirm) {
|
|
341
|
+
console.log(chalk_1.default.dim('Cancelled.'));
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
await (0, api_1.fetchApi)(`/organizations/${username}/members/${user.externalId}`, {
|
|
345
|
+
method: 'DELETE',
|
|
346
|
+
});
|
|
347
|
+
console.log(chalk_1.default.green(`✓ You have left organization '${username}'`));
|
|
348
|
+
// If we were in that org's context, switch to personal
|
|
349
|
+
const context = await (0, api_1.fetchApi)('/users/me/context');
|
|
350
|
+
if (context.type === 'organization' && context.organizationUsername === username) {
|
|
351
|
+
await (0, api_1.fetchApi)('/users/me/context', {
|
|
352
|
+
method: 'PUT',
|
|
353
|
+
body: JSON.stringify({ type: 'personal' }),
|
|
354
|
+
});
|
|
355
|
+
console.log(chalk_1.default.dim(' Switched to personal context.'));
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
catch (error) {
|
|
359
|
+
if (error.name === 'ExitPromptError' || error.message?.includes('force closed')) {
|
|
360
|
+
console.log(chalk_1.default.dim('Cancelled.'));
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
if (error instanceof api_1.AuthenticationError) {
|
|
364
|
+
(0, api_1.handleApiError)(error);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
console.error(chalk_1.default.red('Error:'), error.message);
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
// Sub-command: delete organization
|
|
371
|
+
exports.orgCommand
|
|
372
|
+
.command('delete <username>')
|
|
373
|
+
.description('Delete an organization (owner only)')
|
|
374
|
+
.action(async (username) => {
|
|
375
|
+
try {
|
|
376
|
+
// Confirm with org username
|
|
377
|
+
const { confirmName } = await inquirer_1.default.prompt([
|
|
378
|
+
{
|
|
379
|
+
type: 'input',
|
|
380
|
+
name: 'confirmName',
|
|
381
|
+
message: `Type the organization username "${username}" to confirm deletion:`,
|
|
382
|
+
},
|
|
383
|
+
]);
|
|
384
|
+
if (confirmName !== username) {
|
|
385
|
+
console.log(chalk_1.default.red('Organization name does not match. Aborting.'));
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
await (0, api_1.fetchApi)(`/organizations/${username}`, {
|
|
389
|
+
method: 'DELETE',
|
|
390
|
+
});
|
|
391
|
+
console.log(chalk_1.default.green(`✓ Organization '${username}' has been deleted`));
|
|
392
|
+
// Switch to personal context
|
|
393
|
+
await (0, api_1.fetchApi)('/users/me/context', {
|
|
394
|
+
method: 'PUT',
|
|
395
|
+
body: JSON.stringify({ type: 'personal' }),
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
catch (error) {
|
|
399
|
+
if (error.name === 'ExitPromptError' || error.message?.includes('force closed')) {
|
|
400
|
+
console.log(chalk_1.default.dim('Cancelled.'));
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
if (error instanceof api_1.AuthenticationError) {
|
|
404
|
+
(0, api_1.handleApiError)(error);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
console.error(chalk_1.default.red('Error:'), error.message);
|
|
408
|
+
}
|
|
409
|
+
});
|
package/dist/commands/setup.js
CHANGED
|
@@ -12,6 +12,7 @@ const prompts_1 = require("@inquirer/prompts");
|
|
|
12
12
|
const ora_1 = __importDefault(require("ora"));
|
|
13
13
|
const api_1 = require("../api");
|
|
14
14
|
const claude_auth_1 = require("../utils/claude-auth");
|
|
15
|
+
const gemini_auth_1 = require("../utils/gemini-auth");
|
|
15
16
|
exports.setupCommand = new commander_1.Command('setup')
|
|
16
17
|
.description('Configure user preferences (AI credentials, GitHub, defaults)')
|
|
17
18
|
.option('--show', 'Show current configuration')
|
|
@@ -80,8 +81,30 @@ exports.setupCommand = new commander_1.Command('setup')
|
|
|
80
81
|
console.log(chalk_1.default.dim(`Claude CLI: not found (${claudeCreds.error})`));
|
|
81
82
|
console.log(chalk_1.default.dim(' Run "claude" to authenticate Claude Code first.'));
|
|
82
83
|
}
|
|
83
|
-
//
|
|
84
|
-
|
|
84
|
+
// Gemini CLI
|
|
85
|
+
const geminiCreds = (0, gemini_auth_1.getGeminiCredentials)();
|
|
86
|
+
if (geminiCreds.found) {
|
|
87
|
+
const geminiStatus = current.geminiCredentials
|
|
88
|
+
? chalk_1.default.green('configured')
|
|
89
|
+
: chalk_1.default.yellow('not synced');
|
|
90
|
+
console.log(`Gemini CLI: ${chalk_1.default.green('found locally')} | Server: ${geminiStatus}`);
|
|
91
|
+
const syncGemini = await (0, confirm_1.default)({
|
|
92
|
+
message: current.geminiCredentials
|
|
93
|
+
? 'Update Gemini credentials on server?'
|
|
94
|
+
: 'Sync Gemini credentials to server?',
|
|
95
|
+
default: !current.geminiCredentials,
|
|
96
|
+
});
|
|
97
|
+
if (syncGemini) {
|
|
98
|
+
updates.geminiCredentials = geminiCreds.credentials;
|
|
99
|
+
console.log(chalk_1.default.green(' ✓ Gemini credentials will be synced'));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
console.log(chalk_1.default.dim(`Gemini CLI: not found (${geminiCreds.error})`));
|
|
104
|
+
console.log(chalk_1.default.dim(' Run "gemini" to authenticate Gemini CLI first.'));
|
|
105
|
+
}
|
|
106
|
+
// TODO: Add OpenAI credential detection when their CLI is supported
|
|
107
|
+
console.log(chalk_1.default.dim('\nOpenAI: Coming soon'));
|
|
85
108
|
}
|
|
86
109
|
// GitHub Token
|
|
87
110
|
if (configureGithub) {
|
package/dist/index.js
CHANGED
|
@@ -82,6 +82,7 @@ const logs_1 = require("./commands/logs");
|
|
|
82
82
|
const setup_1 = require("./commands/setup");
|
|
83
83
|
const new_1 = require("./commands/new");
|
|
84
84
|
const username_1 = require("./commands/username");
|
|
85
|
+
const org_1 = require("./commands/org");
|
|
85
86
|
const config_store_1 = require("./config-store");
|
|
86
87
|
const logo_1 = require("./utils/logo");
|
|
87
88
|
const fs = __importStar(require("fs"));
|
|
@@ -314,5 +315,6 @@ program
|
|
|
314
315
|
.addCommand(setup_1.setupCommand)
|
|
315
316
|
.addCommand(new_1.newCommand)
|
|
316
317
|
.addCommand(username_1.usernameCommand)
|
|
318
|
+
.addCommand(org_1.orgCommand)
|
|
317
319
|
.addCommand(completion_1.completeCommand, { hidden: true }); // Internal command for shell completions
|
|
318
320
|
program.parse(process.argv);
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Gemini CLI Authentication Utilities
|
|
4
|
+
*
|
|
5
|
+
* Handles extraction of Gemini CLI credentials from:
|
|
6
|
+
* - ~/.gemini/oauth_creds.json (OAuth credentials from Google login)
|
|
7
|
+
* - GEMINI_API_KEY environment variable
|
|
8
|
+
*
|
|
9
|
+
* These credentials can be injected into genboxes to enable
|
|
10
|
+
* remote Gemini CLI execution using the user's authentication.
|
|
11
|
+
*/
|
|
12
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
15
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
16
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
17
|
+
}
|
|
18
|
+
Object.defineProperty(o, k2, desc);
|
|
19
|
+
}) : (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
o[k2] = m[k];
|
|
22
|
+
}));
|
|
23
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
24
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
25
|
+
}) : function(o, v) {
|
|
26
|
+
o["default"] = v;
|
|
27
|
+
});
|
|
28
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
29
|
+
var ownKeys = function(o) {
|
|
30
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
31
|
+
var ar = [];
|
|
32
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
33
|
+
return ar;
|
|
34
|
+
};
|
|
35
|
+
return ownKeys(o);
|
|
36
|
+
};
|
|
37
|
+
return function (mod) {
|
|
38
|
+
if (mod && mod.__esModule) return mod;
|
|
39
|
+
var result = {};
|
|
40
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
41
|
+
__setModuleDefault(result, mod);
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
})();
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.getGeminiCredentials = getGeminiCredentials;
|
|
47
|
+
exports.getGeminiAuthInstructions = getGeminiAuthInstructions;
|
|
48
|
+
const fs = __importStar(require("fs"));
|
|
49
|
+
const path = __importStar(require("path"));
|
|
50
|
+
const os = __importStar(require("os"));
|
|
51
|
+
/**
|
|
52
|
+
* Get Gemini CLI credentials from the appropriate source.
|
|
53
|
+
*
|
|
54
|
+
* Priority:
|
|
55
|
+
* 1. ~/.gemini/oauth_creds.json (Google OAuth login)
|
|
56
|
+
* 2. GEMINI_API_KEY environment variable
|
|
57
|
+
*/
|
|
58
|
+
function getGeminiCredentials() {
|
|
59
|
+
// Try OAuth credentials file first (from `gemini` login)
|
|
60
|
+
const oauthResult = getCredentialsFromOAuthFile();
|
|
61
|
+
if (oauthResult.found) {
|
|
62
|
+
return oauthResult;
|
|
63
|
+
}
|
|
64
|
+
// Fallback to API key environment variable
|
|
65
|
+
const apiKey = process.env.GEMINI_API_KEY;
|
|
66
|
+
if (apiKey) {
|
|
67
|
+
return {
|
|
68
|
+
credentials: apiKey,
|
|
69
|
+
source: 'apikey',
|
|
70
|
+
found: true,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
// Nothing found
|
|
74
|
+
return oauthResult; // Return the file result which has the error message
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Extract credentials from ~/.gemini/oauth_creds.json
|
|
78
|
+
*/
|
|
79
|
+
function getCredentialsFromOAuthFile() {
|
|
80
|
+
const credentialsPath = path.join(os.homedir(), '.gemini', 'oauth_creds.json');
|
|
81
|
+
if (!fs.existsSync(credentialsPath)) {
|
|
82
|
+
return {
|
|
83
|
+
credentials: '',
|
|
84
|
+
source: 'oauth',
|
|
85
|
+
found: false,
|
|
86
|
+
error: `Gemini credentials file not found at ${credentialsPath}. Run 'gemini' to authenticate first.`,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const content = fs.readFileSync(credentialsPath, 'utf-8');
|
|
91
|
+
// Validate it's valid JSON with expected fields
|
|
92
|
+
const parsed = JSON.parse(content);
|
|
93
|
+
if (!parsed.access_token && !parsed.refresh_token) {
|
|
94
|
+
return {
|
|
95
|
+
credentials: '',
|
|
96
|
+
source: 'oauth',
|
|
97
|
+
found: false,
|
|
98
|
+
error: 'Gemini credentials file exists but appears invalid (missing tokens).',
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
credentials: content,
|
|
103
|
+
source: 'oauth',
|
|
104
|
+
found: true,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
return {
|
|
109
|
+
credentials: '',
|
|
110
|
+
source: 'oauth',
|
|
111
|
+
found: false,
|
|
112
|
+
error: `Failed to read Gemini credentials: ${err.message}`,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get instructions for authenticating Gemini CLI
|
|
118
|
+
*/
|
|
119
|
+
function getGeminiAuthInstructions() {
|
|
120
|
+
return `To authenticate Gemini CLI:
|
|
121
|
+
1. Install Gemini CLI: npm install -g @anthropic-ai/gemini-cli
|
|
122
|
+
2. Run 'gemini' in your terminal
|
|
123
|
+
3. Follow the browser authentication flow
|
|
124
|
+
4. Your credentials will be stored in ~/.gemini/oauth_creds.json
|
|
125
|
+
|
|
126
|
+
Alternative: Set GEMINI_API_KEY environment variable with your API key.`;
|
|
127
|
+
}
|
package/dist/utils/index.js
CHANGED
|
@@ -23,4 +23,5 @@ __exportStar(require("./env-parser"), exports);
|
|
|
23
23
|
__exportStar(require("./git-utils"), exports);
|
|
24
24
|
__exportStar(require("./branch-prompt"), exports);
|
|
25
25
|
__exportStar(require("./claude-auth"), exports);
|
|
26
|
+
__exportStar(require("./gemini-auth"), exports);
|
|
26
27
|
__exportStar(require("./logo"), exports);
|