gemix 0.0.1 → 0.0.2
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/auth/oauth.d.ts.map +1 -1
- package/dist/auth/oauth.js +103 -141
- package/dist/auth/oauth.js.map +1 -1
- package/dist/commands/current.js +2 -2
- package/dist/commands/current.js.map +1 -1
- package/dist/commands/list.js +1 -1
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/register.js +1 -1
- package/dist/commands/register.js.map +1 -1
- package/dist/commands/remove.js +1 -1
- package/dist/commands/remove.js.map +1 -1
- package/dist/commands/use.js +1 -1
- package/dist/commands/use.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/storage/paths.js +1 -1
- package/dist/storage/paths.js.map +1 -1
- package/doc/README.md +20 -20
- package/package.json +9 -4
- package/src/auth/oauth.ts +121 -158
- package/src/commands/current.ts +2 -2
- package/src/commands/list.ts +1 -1
- package/src/commands/register.ts +1 -1
- package/src/commands/remove.ts +1 -1
- package/src/commands/use.ts +1 -1
- package/src/index.ts +1 -1
- package/src/storage/paths.ts +1 -1
package/dist/auth/oauth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../src/auth/oauth.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../src/auth/oauth.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AA0BxD,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,SAAS,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,WAAW,CAAC,CAuFnE;AAoCD,wBAAsB,aAAa,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAkChF"}
|
package/dist/auth/oauth.js
CHANGED
|
@@ -1,182 +1,144 @@
|
|
|
1
|
-
import { OAuth2Client } from 'google-auth-library';
|
|
2
|
-
import * as http from 'node:http';
|
|
3
|
-
import * as url from 'node:url';
|
|
4
|
-
import * as net from 'node:net';
|
|
5
1
|
import * as crypto from 'node:crypto';
|
|
2
|
+
import * as readline from 'node:readline';
|
|
6
3
|
import open from 'open';
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
const OAUTH_CLIENT_SECRET = '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';
|
|
4
|
+
const OAUTH_CLIENT_ID = '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com';
|
|
5
|
+
const OAUTH_CLIENT_SECRET = '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';
|
|
10
6
|
const OAUTH_SCOPE = [
|
|
11
7
|
'openid',
|
|
12
8
|
'https://www.googleapis.com/auth/cloud-platform',
|
|
13
9
|
'https://www.googleapis.com/auth/userinfo.email',
|
|
14
10
|
'https://www.googleapis.com/auth/userinfo.profile',
|
|
11
|
+
'https://www.googleapis.com/auth/cclog',
|
|
12
|
+
'https://www.googleapis.com/auth/experimentsandconfigs',
|
|
15
13
|
];
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
server.listen(0, () => {
|
|
23
|
-
const address = server.address();
|
|
24
|
-
const port = address && typeof address === 'object' ? address.port : 0;
|
|
25
|
-
server.close(() => resolve(port));
|
|
26
|
-
});
|
|
27
|
-
server.on('error', reject);
|
|
28
|
-
});
|
|
14
|
+
const REDIRECT_URI = 'https://antigravity.google/oauth-callback';
|
|
15
|
+
function generateCodeVerifier() {
|
|
16
|
+
return crypto.randomBytes(32).toString('base64url');
|
|
17
|
+
}
|
|
18
|
+
function generateCodeChallenge(verifier) {
|
|
19
|
+
return crypto.createHash('sha256').update(verifier).digest('base64url');
|
|
29
20
|
}
|
|
30
21
|
export async function authenticateWithGoogle() {
|
|
31
|
-
const client = new OAuth2Client({
|
|
32
|
-
clientId: OAUTH_CLIENT_ID,
|
|
33
|
-
clientSecret: OAUTH_CLIENT_SECRET,
|
|
34
|
-
});
|
|
35
|
-
const port = await getAvailablePort();
|
|
36
|
-
const redirectUri = `http://127.0.0.1:${port}/oauth2callback`;
|
|
37
22
|
const state = crypto.randomBytes(32).toString('hex');
|
|
38
|
-
const
|
|
39
|
-
|
|
23
|
+
const codeVerifier = generateCodeVerifier();
|
|
24
|
+
const codeChallenge = generateCodeChallenge(codeVerifier);
|
|
25
|
+
const authParams = new URLSearchParams({
|
|
26
|
+
client_id: OAUTH_CLIENT_ID,
|
|
27
|
+
response_type: 'code',
|
|
28
|
+
redirect_uri: REDIRECT_URI,
|
|
29
|
+
scope: OAUTH_SCOPE.join(' '),
|
|
40
30
|
access_type: 'offline',
|
|
41
|
-
scope: OAUTH_SCOPE,
|
|
42
31
|
prompt: 'consent',
|
|
43
32
|
state,
|
|
33
|
+
code_challenge: codeChallenge,
|
|
34
|
+
code_challenge_method: 'S256',
|
|
44
35
|
});
|
|
45
|
-
const
|
|
46
|
-
const server = http.createServer(async (req, res) => {
|
|
47
|
-
try {
|
|
48
|
-
if (!req.url || req.url.indexOf('/oauth2callback') === -1) {
|
|
49
|
-
res.writeHead(HTTP_REDIRECT, { Location: SIGN_IN_FAILURE_URL });
|
|
50
|
-
res.end();
|
|
51
|
-
reject(new Error('Unexpected request: ' + req.url));
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
const qs = new url.URL(req.url, `http://127.0.0.1:${port}`).searchParams;
|
|
55
|
-
if (qs.get('error')) {
|
|
56
|
-
res.writeHead(HTTP_REDIRECT, { Location: SIGN_IN_FAILURE_URL });
|
|
57
|
-
res.end();
|
|
58
|
-
reject(new Error(`Google OAuth error: ${qs.get('error')}. ${qs.get('error_description') || ''}`));
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
if (qs.get('state') !== state) {
|
|
62
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
63
|
-
res.end('<h1>State mismatch. Possible CSRF attack.</h1>');
|
|
64
|
-
reject(new Error('State mismatch. Possible CSRF attack.'));
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
if (qs.get('code')) {
|
|
68
|
-
res.writeHead(HTTP_REDIRECT, { Location: SIGN_IN_SUCCESS_URL });
|
|
69
|
-
res.end();
|
|
70
|
-
resolve({ code: qs.get('code') });
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
res.writeHead(HTTP_REDIRECT, { Location: SIGN_IN_FAILURE_URL });
|
|
74
|
-
res.end();
|
|
75
|
-
reject(new Error('No authorization code received.'));
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
catch (e) {
|
|
79
|
-
res.writeHead(HTTP_REDIRECT, { Location: SIGN_IN_FAILURE_URL });
|
|
80
|
-
res.end();
|
|
81
|
-
reject(e instanceof Error ? e : new Error(String(e)));
|
|
82
|
-
}
|
|
83
|
-
finally {
|
|
84
|
-
server.close();
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
server.listen(port, '127.0.0.1', () => { });
|
|
88
|
-
server.on('error', reject);
|
|
89
|
-
});
|
|
36
|
+
const authUrl = `https://accounts.google.com/o/oauth2/auth?${authParams.toString()}`;
|
|
90
37
|
console.log('\nOpening browser for Google authentication...\n');
|
|
38
|
+
console.log('After signing in, copy the authorization code from the redirect page.\n');
|
|
91
39
|
console.log(`If the browser doesn't open, visit:\n${authUrl}\n`);
|
|
92
40
|
const childProcess = await open(authUrl);
|
|
93
41
|
childProcess.on('error', (error) => {
|
|
94
42
|
console.error(`Failed to open browser: ${error.message}`);
|
|
95
43
|
console.log(`Please visit:\n${authUrl}\n`);
|
|
96
44
|
});
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
try {
|
|
110
|
-
const { code } = await Promise.race([
|
|
111
|
-
loginCompletePromise,
|
|
112
|
-
timeoutPromise,
|
|
113
|
-
cancellationPromise,
|
|
114
|
-
]);
|
|
115
|
-
if (timeoutId)
|
|
116
|
-
clearTimeout(timeoutId);
|
|
117
|
-
const { tokens } = await client.getToken({
|
|
45
|
+
const code = await promptForAuthCode();
|
|
46
|
+
const startTime = Date.now();
|
|
47
|
+
const tokenResponse = await fetch('https://oauth2.googleapis.com/token', {
|
|
48
|
+
method: 'POST',
|
|
49
|
+
headers: {
|
|
50
|
+
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
|
|
51
|
+
},
|
|
52
|
+
body: new URLSearchParams({
|
|
53
|
+
client_id: OAUTH_CLIENT_ID,
|
|
54
|
+
client_secret: OAUTH_CLIENT_SECRET,
|
|
118
55
|
code,
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
id_token: tokens.id_token || '',
|
|
128
|
-
scope: tokens.scope || OAUTH_SCOPE.join(' '),
|
|
129
|
-
token_type: tokens.token_type || 'Bearer',
|
|
130
|
-
expiry_date: tokens.expiry_date || Date.now(),
|
|
131
|
-
},
|
|
132
|
-
email: userInfo.email,
|
|
133
|
-
name: userInfo.name,
|
|
134
|
-
picture: userInfo.picture,
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
finally {
|
|
138
|
-
if (timeoutId)
|
|
139
|
-
clearTimeout(timeoutId);
|
|
56
|
+
grant_type: 'authorization_code',
|
|
57
|
+
redirect_uri: REDIRECT_URI,
|
|
58
|
+
code_verifier: codeVerifier,
|
|
59
|
+
}),
|
|
60
|
+
});
|
|
61
|
+
if (!tokenResponse.ok) {
|
|
62
|
+
const errorText = await tokenResponse.text();
|
|
63
|
+
throw new Error(`Token exchange failed: ${tokenResponse.status} ${errorText}`);
|
|
140
64
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (!token)
|
|
145
|
-
throw new Error('No access token available');
|
|
146
|
-
const response = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
|
|
147
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
65
|
+
const tokenPayload = (await tokenResponse.json());
|
|
66
|
+
const userInfoResponse = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
|
|
67
|
+
headers: { Authorization: `Bearer ${tokenPayload.access_token}` },
|
|
148
68
|
});
|
|
149
|
-
if (!
|
|
150
|
-
throw new Error(`Failed to fetch user info: ${
|
|
69
|
+
if (!userInfoResponse.ok) {
|
|
70
|
+
throw new Error(`Failed to fetch user info: ${userInfoResponse.status} ${userInfoResponse.statusText}`);
|
|
151
71
|
}
|
|
152
|
-
const userInfo = (await
|
|
72
|
+
const userInfo = (await userInfoResponse.json());
|
|
153
73
|
return {
|
|
74
|
+
tokens: {
|
|
75
|
+
access_token: tokenPayload.access_token,
|
|
76
|
+
refresh_token: tokenPayload.refresh_token,
|
|
77
|
+
id_token: '',
|
|
78
|
+
scope: tokenPayload.scope || OAUTH_SCOPE.join(' '),
|
|
79
|
+
token_type: tokenPayload.token_type || 'Bearer',
|
|
80
|
+
expiry_date: startTime + tokenPayload.expires_in * 1000,
|
|
81
|
+
},
|
|
154
82
|
email: userInfo.email,
|
|
155
83
|
name: userInfo.name,
|
|
156
84
|
picture: userInfo.picture,
|
|
157
85
|
};
|
|
158
86
|
}
|
|
87
|
+
function promptForAuthCode() {
|
|
88
|
+
return new Promise((resolve, reject) => {
|
|
89
|
+
const rl = readline.createInterface({
|
|
90
|
+
input: process.stdin,
|
|
91
|
+
output: process.stdout,
|
|
92
|
+
});
|
|
93
|
+
const authTimeout = 5 * 60 * 1000;
|
|
94
|
+
const timeoutId = setTimeout(() => {
|
|
95
|
+
rl.close();
|
|
96
|
+
reject(new Error('Authentication timed out after 5 minutes.'));
|
|
97
|
+
}, authTimeout);
|
|
98
|
+
const onSigint = () => {
|
|
99
|
+
clearTimeout(timeoutId);
|
|
100
|
+
rl.close();
|
|
101
|
+
reject(new Error('Authentication cancelled by user.'));
|
|
102
|
+
};
|
|
103
|
+
process.on('SIGINT', onSigint);
|
|
104
|
+
rl.question('Paste the authorization code: ', (answer) => {
|
|
105
|
+
clearTimeout(timeoutId);
|
|
106
|
+
process.removeListener('SIGINT', onSigint);
|
|
107
|
+
rl.close();
|
|
108
|
+
const code = answer.trim();
|
|
109
|
+
if (!code) {
|
|
110
|
+
reject(new Error('No authorization code provided.'));
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
resolve(code);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
}
|
|
159
118
|
export async function refreshTokens(tokens) {
|
|
160
119
|
try {
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
120
|
+
const tokenResponse = await fetch('https://oauth2.googleapis.com/token', {
|
|
121
|
+
method: 'POST',
|
|
122
|
+
headers: {
|
|
123
|
+
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
|
|
124
|
+
},
|
|
125
|
+
body: new URLSearchParams({
|
|
126
|
+
client_id: OAUTH_CLIENT_ID,
|
|
127
|
+
client_secret: OAUTH_CLIENT_SECRET,
|
|
128
|
+
refresh_token: tokens.refresh_token,
|
|
129
|
+
grant_type: 'refresh_token',
|
|
130
|
+
}),
|
|
168
131
|
});
|
|
169
|
-
|
|
170
|
-
if (!newAccessToken)
|
|
132
|
+
if (!tokenResponse.ok)
|
|
171
133
|
return null;
|
|
172
|
-
const
|
|
134
|
+
const tokenPayload = (await tokenResponse.json());
|
|
173
135
|
return {
|
|
174
|
-
access_token:
|
|
175
|
-
refresh_token:
|
|
176
|
-
id_token:
|
|
136
|
+
access_token: tokenPayload.access_token,
|
|
137
|
+
refresh_token: tokens.refresh_token,
|
|
138
|
+
id_token: tokens.id_token || '',
|
|
177
139
|
scope: tokens.scope,
|
|
178
|
-
token_type:
|
|
179
|
-
expiry_date:
|
|
140
|
+
token_type: tokenPayload.token_type || 'Bearer',
|
|
141
|
+
expiry_date: Date.now() + tokenPayload.expires_in * 1000,
|
|
180
142
|
};
|
|
181
143
|
}
|
|
182
144
|
catch {
|
package/dist/auth/oauth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../src/auth/oauth.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../src/auth/oauth.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/current.js
CHANGED
|
@@ -5,8 +5,8 @@ export async function currentCommand() {
|
|
|
5
5
|
const accounts = await loadAccounts();
|
|
6
6
|
if (!activeUsername || !accounts.accounts[activeUsername]) {
|
|
7
7
|
console.log('No active account.');
|
|
8
|
-
console.log('Use "
|
|
9
|
-
console.log('Use "
|
|
8
|
+
console.log('Use "gemix list" to see available accounts.');
|
|
9
|
+
console.log('Use "gemix use <username>" to switch accounts.');
|
|
10
10
|
return;
|
|
11
11
|
}
|
|
12
12
|
const account = accounts.accounts[activeUsername];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"current.js","sourceRoot":"","sources":["../../src/commands/current.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,cAAc,GAAG,MAAM,iBAAiB,EAAE,CAAC;IACjD,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAC;IAEtC,IAAI,CAAC,cAAc,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;QAC1D,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;QAClC,OAAO,CAAC,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"current.js","sourceRoot":"","sources":["../../src/commands/current.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,cAAc,GAAG,MAAM,iBAAiB,EAAE,CAAC;IACjD,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAC;IAEtC,IAAI,CAAC,cAAc,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;QAC1D,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;QAClC,OAAO,CAAC,GAAG,CAAC,6CAA6C,CAAC,CAAC;QAC3D,OAAO,CAAC,GAAG,CAAC,gDAAgD,CAAC,CAAC;QAC9D,OAAO;IACT,CAAC;IAED,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;IAClD,OAAO,CAAC,GAAG,CAAC,mBAAmB,cAAc,KAAK,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC;IACpE,OAAO,CAAC,GAAG,CAAC,WAAW,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AACzD,CAAC"}
|
package/dist/commands/list.js
CHANGED
|
@@ -5,7 +5,7 @@ export async function listCommand(json = false) {
|
|
|
5
5
|
const activeUsername = accounts.active;
|
|
6
6
|
if (Object.keys(accounts.accounts).length === 0) {
|
|
7
7
|
console.log('No accounts registered.');
|
|
8
|
-
console.log('Run "
|
|
8
|
+
console.log('Run "gemix register <username>" to add an account.');
|
|
9
9
|
return;
|
|
10
10
|
}
|
|
11
11
|
if (json) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/commands/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAE/E,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAgB,KAAK;IACrD,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAC;IACtC,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC;IAEvC,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChD,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;QACvC,OAAO,CAAC,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/commands/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAE/E,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAgB,KAAK;IACrD,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAC;IACtC,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC;IAEvC,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChD,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;QACvC,OAAO,CAAC,GAAG,CAAC,oDAAoD,CAAC,CAAC;QAClE,OAAO;IACT,CAAC;IAED,IAAI,IAAI,EAAE,CAAC;QACT,MAAM,MAAM,GAAG,sBAAsB,CAAC,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;QACzE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC/C,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAC;IACrE,CAAC;AACH,CAAC"}
|
|
@@ -11,7 +11,7 @@ export async function registerCommand(username) {
|
|
|
11
11
|
const accounts = await loadAccounts();
|
|
12
12
|
if (accounts.accounts[username]) {
|
|
13
13
|
console.error(`Error: Account '${username}' already exists.`);
|
|
14
|
-
console.error(`Use '
|
|
14
|
+
console.error(`Use 'gemix remove ${username}' to remove it first.`);
|
|
15
15
|
process.exit(1);
|
|
16
16
|
}
|
|
17
17
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register.js","sourceRoot":"","sources":["../../src/commands/register.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,QAAgB;IACpD,MAAM,UAAU,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAC9C,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;QACtB,OAAO,CAAC,KAAK,CAAC,UAAU,UAAU,CAAC,KAAK,EAAE,CAAC,CAAC;QAC5C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAC;IACtC,IAAI,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QAChC,OAAO,CAAC,KAAK,CAAC,mBAAmB,QAAQ,mBAAmB,CAAC,CAAC;QAC9D,OAAO,CAAC,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"register.js","sourceRoot":"","sources":["../../src/commands/register.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,QAAgB;IACpD,MAAM,UAAU,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAC9C,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;QACtB,OAAO,CAAC,KAAK,CAAC,UAAU,UAAU,CAAC,KAAK,EAAE,CAAC,CAAC;QAC5C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAC;IACtC,IAAI,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QAChC,OAAO,CAAC,KAAK,CAAC,mBAAmB,QAAQ,mBAAmB,CAAC,CAAC;QAC9D,OAAO,CAAC,KAAK,CAAC,qBAAqB,QAAQ,uBAAuB,CAAC,CAAC;QACpE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,wBAAwB,QAAQ,MAAM,CAAC,CAAC;QACpD,MAAM,MAAM,GAAG,MAAM,sBAAsB,EAAE,CAAC;QAE9C,MAAM,UAAU,CACd,QAAQ,EACR,MAAM,CAAC,KAAK,EACZ,MAAM,CAAC,IAAI,EACX,MAAM,CAAC,OAAO,EACd,MAAM,CAAC,MAAM,CACd,CAAC;QAEF,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC;QAEnE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,gBAAgB,QAAQ,KAAK,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC;QAC1D,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,OAAO,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;QACrC,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,aAAa,YAAY,CAAC,EAAE,cAAc,EAAE,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;QAEzF,IAAI,cAAc,EAAE,CAAC;YACnB,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;QACzC,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAClB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;YAClE,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;QACzC,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,sBAAsB,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QACxF,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
|
package/dist/commands/remove.js
CHANGED
|
@@ -4,7 +4,7 @@ export async function removeCommand(username, yes = false) {
|
|
|
4
4
|
const accounts = await loadAccounts();
|
|
5
5
|
if (!accounts.accounts[username]) {
|
|
6
6
|
console.error(`Error: Account '${username}' not found.`);
|
|
7
|
-
console.error('Use "
|
|
7
|
+
console.error('Use "gemix list" to see available accounts.');
|
|
8
8
|
process.exit(1);
|
|
9
9
|
}
|
|
10
10
|
if (!yes) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remove.js","sourceRoot":"","sources":["../../src/commands/remove.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAErE,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,QAAgB,EAAE,MAAe,KAAK;IACxE,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAC;IAEtC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjC,OAAO,CAAC,KAAK,CAAC,mBAAmB,QAAQ,cAAc,CAAC,CAAC;QACzD,OAAO,CAAC,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"remove.js","sourceRoot":"","sources":["../../src/commands/remove.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAErE,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,QAAgB,EAAE,MAAe,KAAK;IACxE,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAC;IAEtC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjC,OAAO,CAAC,KAAK,CAAC,mBAAmB,QAAQ,cAAc,CAAC,CAAC;QACzD,OAAO,CAAC,KAAK,CAAC,6CAA6C,CAAC,CAAC;QAC7D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC;YAClC,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,MAAM,EAAE,OAAO,CAAC,MAAM;SACvB,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,EAAE;YACnD,EAAE,CAAC,QAAQ,CAAC,mBAAmB,QAAQ,WAAW,EAAE,OAAO,CAAC,CAAC;QAC/D,CAAC,CAAC,CAAC;QACH,EAAE,CAAC,KAAK,EAAE,CAAC;QAEX,IAAI,MAAM,CAAC,WAAW,EAAE,KAAK,GAAG,IAAI,MAAM,CAAC,WAAW,EAAE,KAAK,KAAK,EAAE,CAAC;YACnE,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;YAClC,OAAO;QACT,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC9C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;QAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,sBAAsB,QAAQ,GAAG,CAAC,CAAC;AACjD,CAAC"}
|
package/dist/commands/use.js
CHANGED
|
@@ -3,7 +3,7 @@ export async function useCommand(username) {
|
|
|
3
3
|
const accounts = await loadAccounts();
|
|
4
4
|
if (!accounts.accounts[username]) {
|
|
5
5
|
console.error(`Error: Account '${username}' not found.`);
|
|
6
|
-
console.error('Use "
|
|
6
|
+
console.error('Use "gemix list" to see available accounts.');
|
|
7
7
|
process.exit(1);
|
|
8
8
|
}
|
|
9
9
|
try {
|
package/dist/commands/use.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use.js","sourceRoot":"","sources":["../../src/commands/use.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAErE,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,QAAgB;IAC/C,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAC;IAEtC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjC,OAAO,CAAC,KAAK,CAAC,mBAAmB,QAAQ,cAAc,CAAC,CAAC;QACzD,OAAO,CAAC,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"use.js","sourceRoot":"","sources":["../../src/commands/use.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAErE,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,QAAgB;IAC/C,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAC;IAEtC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjC,OAAO,CAAC,KAAK,CAAC,mBAAmB,QAAQ,cAAc,CAAC,CAAC;QACzD,OAAO,CAAC,KAAK,CAAC,6CAA6C,CAAC,CAAC;QAC7D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC9C,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;YAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC5C,OAAO,CAAC,GAAG,CAAC,iBAAiB,QAAQ,KAAK,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC;IAC9D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QACxE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { currentCommand } from './commands/current.js';
|
|
|
7
7
|
import { removeCommand } from './commands/remove.js';
|
|
8
8
|
const program = new Command();
|
|
9
9
|
program
|
|
10
|
-
.name('
|
|
10
|
+
.name('gemix')
|
|
11
11
|
.description('Gemini multi-account CLI - manage multiple Google accounts')
|
|
12
12
|
.version('1.0.0');
|
|
13
13
|
program
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,OAAO,CAAC;KACb,WAAW,CAAC,4DAA4D,CAAC;KACzE,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO;KACJ,OAAO,CAAC,UAAU,CAAC;KACnB,WAAW,CAAC,+BAA+B,CAAC;KAC5C,QAAQ,CAAC,YAAY,EAAE,kCAAkC,CAAC;KAC1D,MAAM,CAAC,KAAK,EAAE,QAAgB,EAAE,EAAE;IACjC,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAC;AAClC,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,KAAK,CAAC,IAAI,CAAC;KACX,WAAW,CAAC,8BAA8B,CAAC;KAC3C,MAAM,CAAC,YAAY,EAAE,uBAAuB,CAAC;KAC7C,MAAM,CAAC,KAAK,EAAE,OAA2B,EAAE,EAAE;IAC5C,MAAM,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AAClC,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,KAAK,CAAC;KACd,WAAW,CAAC,+BAA+B,CAAC;KAC5C,QAAQ,CAAC,YAAY,EAAE,sCAAsC,CAAC;KAC9D,MAAM,CAAC,KAAK,EAAE,QAAgB,EAAE,EAAE;IACjC,MAAM,UAAU,CAAC,QAAQ,CAAC,CAAC;AAC7B,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,SAAS,CAAC;KAClB,WAAW,CAAC,mCAAmC,CAAC;KAChD,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,MAAM,cAAc,EAAE,CAAC;AACzB,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,KAAK,CAAC,IAAI,CAAC;KACX,WAAW,CAAC,6BAA6B,CAAC;KAC1C,QAAQ,CAAC,YAAY,EAAE,mCAAmC,CAAC;KAC3D,MAAM,CAAC,WAAW,EAAE,mBAAmB,CAAC;KACxC,MAAM,CAAC,KAAK,EAAE,QAAgB,EAAE,OAA0B,EAAE,EAAE;IAC7D,MAAM,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;AAC7C,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
package/dist/storage/paths.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as path from 'node:path';
|
|
2
2
|
import * as os from 'node:os';
|
|
3
3
|
import { promises as fs } from 'node:fs';
|
|
4
|
-
const GEMX_DIR = '.
|
|
4
|
+
const GEMX_DIR = '.gemix';
|
|
5
5
|
const GEMINI_DIR = '.gemini';
|
|
6
6
|
const ACCOUNTS_FILE = 'accounts.json';
|
|
7
7
|
const ACTIVE_FILE = 'active';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/storage/paths.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,QAAQ,GAAG,
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/storage/paths.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,QAAQ,GAAG,QAAQ,CAAC;AAC1B,MAAM,UAAU,GAAG,SAAS,CAAC;AAC7B,MAAM,aAAa,GAAG,eAAe,CAAC;AACtC,MAAM,WAAW,GAAG,QAAQ,CAAC;AAC7B,MAAM,YAAY,GAAG,UAAU,CAAC;AAChC,MAAM,gBAAgB,GAAG,kBAAkB,CAAC;AAC5C,MAAM,oBAAoB,GAAG,sBAAsB,CAAC;AAEpD,MAAM,UAAU,UAAU;IACxB,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,aAAa,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,WAAW,CAAC,CAAC;AAC9C,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,YAAY,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,OAAO,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,QAAQ,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,QAAgB;IACnD,OAAO,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,EAAE,aAAa,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,uBAAuB;IACrC,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,gBAAgB,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,2BAA2B;IACzC,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,oBAAoB,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,4BAA4B;IAC1C,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,iBAAiB,EAAE,yBAAyB,CAAC,CAAC;AAC3F,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,MAAM,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,MAAM,EAAE,CAAC,KAAK,CAAC,cAAc,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AACxD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,QAAgB;IACrD,MAAM,EAAE,CAAC,KAAK,CAAC,aAAa,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAC/D,CAAC"}
|
package/doc/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# gemix - Gemini Multi-Account CLI
|
|
2
2
|
|
|
3
3
|
Manage multiple Google accounts for Gemini API access from a single CLI tool.
|
|
4
4
|
|
|
@@ -8,7 +8,7 @@ Manage multiple Google accounts for Gemini API access from a single CLI tool.
|
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
git clone <repo-url>
|
|
11
|
-
cd
|
|
11
|
+
cd gemix
|
|
12
12
|
npm install
|
|
13
13
|
npm run build
|
|
14
14
|
npm link
|
|
@@ -23,46 +23,46 @@ npm link
|
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
25
|
# Register your first account
|
|
26
|
-
|
|
26
|
+
gemix register personal
|
|
27
27
|
|
|
28
28
|
# Register another account
|
|
29
|
-
|
|
29
|
+
gemix register work
|
|
30
30
|
|
|
31
31
|
# List all accounts
|
|
32
|
-
|
|
32
|
+
gemix list
|
|
33
33
|
|
|
34
34
|
# Switch accounts
|
|
35
|
-
|
|
35
|
+
gemix use work
|
|
36
36
|
|
|
37
37
|
# Check active account
|
|
38
|
-
|
|
38
|
+
gemix current
|
|
39
39
|
|
|
40
40
|
# Remove an account
|
|
41
|
-
|
|
41
|
+
gemix remove personal
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
## Commands
|
|
45
45
|
|
|
46
46
|
| Command | Description |
|
|
47
47
|
|---------|-------------|
|
|
48
|
-
| `
|
|
49
|
-
| `
|
|
50
|
-
| `
|
|
51
|
-
| `
|
|
52
|
-
| `
|
|
53
|
-
| `
|
|
48
|
+
| `gemix register <username>` | Register a new Google account via OAuth |
|
|
49
|
+
| `gemix list` | List all registered accounts |
|
|
50
|
+
| `gemix list --json` | Output in JSON format |
|
|
51
|
+
| `gemix use <username>` | Switch active account |
|
|
52
|
+
| `gemix current` | Show active account |
|
|
53
|
+
| `gemix remove <username>` | Remove an account |
|
|
54
54
|
|
|
55
55
|
## How It Works
|
|
56
56
|
|
|
57
57
|
### Storage
|
|
58
58
|
|
|
59
|
-
- `~/.
|
|
60
|
-
- `~/.
|
|
59
|
+
- `~/.gemix/accounts.json` - Master registry of all accounts
|
|
60
|
+
- `~/.gemix/accounts/<username>/tokens.json` - Per-account OAuth tokens
|
|
61
61
|
- `~/.gemini/oauth_creds.json` - Gemini CLI reads from here
|
|
62
62
|
|
|
63
63
|
### Account Switching
|
|
64
64
|
|
|
65
|
-
When you run `
|
|
65
|
+
When you run `gemix use <username>`, it copies that account's tokens to `~/.gemini/oauth_creds.json` where Gemini CLI expects them.
|
|
66
66
|
|
|
67
67
|
### OAuth Flow
|
|
68
68
|
|
|
@@ -79,11 +79,11 @@ Uses the same OAuth Client ID as Gemini CLI (public for installed apps). Your cr
|
|
|
79
79
|
|
|
80
80
|
| Issue | Solution |
|
|
81
81
|
|-------|----------|
|
|
82
|
-
| "Account already exists" | Run `
|
|
83
|
-
| "Account not found" | Run `
|
|
82
|
+
| "Account already exists" | Run `gemix remove <username>` first |
|
|
83
|
+
| "Account not found" | Run `gemix list` to see available accounts |
|
|
84
84
|
| Browser doesn't open | Visit the URL shown in the terminal manually |
|
|
85
85
|
| Auth timed out | Try again, complete login within 5 minutes |
|
|
86
|
-
| "No active account" | Run `
|
|
86
|
+
| "No active account" | Run `gemix register <username>` to add one |
|
|
87
87
|
|
|
88
88
|
## License
|
|
89
89
|
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gemix",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "Gemini multi-account CLI - manage multiple Google accounts for Gemini API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"bin": {
|
|
8
|
-
"
|
|
8
|
+
"gemix": "./dist/index.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"build": "tsc",
|
|
@@ -13,12 +13,17 @@
|
|
|
13
13
|
"start": "node dist/index.js",
|
|
14
14
|
"link": "npm run build && npm link"
|
|
15
15
|
},
|
|
16
|
-
"keywords": [
|
|
16
|
+
"keywords": [
|
|
17
|
+
"gemini",
|
|
18
|
+
"google",
|
|
19
|
+
"oauth",
|
|
20
|
+
"multi-account",
|
|
21
|
+
"cli"
|
|
22
|
+
],
|
|
17
23
|
"author": "",
|
|
18
24
|
"license": "MIT",
|
|
19
25
|
"dependencies": {
|
|
20
26
|
"commander": "^12.1.0",
|
|
21
|
-
"google-auth-library": "^9.15.1",
|
|
22
27
|
"open": "^10.1.0",
|
|
23
28
|
"cli-table3": "^0.6.5"
|
|
24
29
|
},
|
package/src/auth/oauth.ts
CHANGED
|
@@ -1,29 +1,31 @@
|
|
|
1
|
-
import { OAuth2Client } from 'google-auth-library';
|
|
2
|
-
import * as http from 'node:http';
|
|
3
|
-
import * as url from 'node:url';
|
|
4
|
-
import * as net from 'node:net';
|
|
5
1
|
import * as crypto from 'node:crypto';
|
|
2
|
+
import * as readline from 'node:readline';
|
|
6
3
|
import open from 'open';
|
|
7
4
|
import type { TokenData } from '../storage/accounts.js';
|
|
8
5
|
|
|
9
|
-
// OAuth Client ID (same as Gemini CLI - public for installed apps)
|
|
10
6
|
const OAUTH_CLIENT_ID =
|
|
11
|
-
'
|
|
7
|
+
'1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com';
|
|
12
8
|
|
|
13
|
-
const OAUTH_CLIENT_SECRET = 'GOCSPX-
|
|
9
|
+
const OAUTH_CLIENT_SECRET = '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';
|
|
14
10
|
|
|
15
11
|
const OAUTH_SCOPE = [
|
|
16
12
|
'openid',
|
|
17
13
|
'https://www.googleapis.com/auth/cloud-platform',
|
|
18
14
|
'https://www.googleapis.com/auth/userinfo.email',
|
|
19
15
|
'https://www.googleapis.com/auth/userinfo.profile',
|
|
16
|
+
'https://www.googleapis.com/auth/cclog',
|
|
17
|
+
'https://www.googleapis.com/auth/experimentsandconfigs',
|
|
20
18
|
];
|
|
21
19
|
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
const REDIRECT_URI = 'https://antigravity.google/oauth-callback';
|
|
21
|
+
|
|
22
|
+
function generateCodeVerifier(): string {
|
|
23
|
+
return crypto.randomBytes(32).toString('base64url');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function generateCodeChallenge(verifier: string): string {
|
|
27
|
+
return crypto.createHash('sha256').update(verifier).digest('base64url');
|
|
28
|
+
}
|
|
27
29
|
|
|
28
30
|
export interface OAuthResult {
|
|
29
31
|
tokens: TokenData;
|
|
@@ -32,87 +34,27 @@ export interface OAuthResult {
|
|
|
32
34
|
picture?: string;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
function getAvailablePort(): Promise<number> {
|
|
36
|
-
return new Promise((resolve, reject) => {
|
|
37
|
-
const server = net.createServer();
|
|
38
|
-
server.listen(0, () => {
|
|
39
|
-
const address = server.address();
|
|
40
|
-
const port = address && typeof address === 'object' ? address.port : 0;
|
|
41
|
-
server.close(() => resolve(port));
|
|
42
|
-
});
|
|
43
|
-
server.on('error', reject);
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
|
|
47
37
|
export async function authenticateWithGoogle(): Promise<OAuthResult> {
|
|
48
|
-
const client = new OAuth2Client({
|
|
49
|
-
clientId: OAUTH_CLIENT_ID,
|
|
50
|
-
clientSecret: OAUTH_CLIENT_SECRET,
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
const port = await getAvailablePort();
|
|
54
|
-
const redirectUri = `http://127.0.0.1:${port}/oauth2callback`;
|
|
55
38
|
const state = crypto.randomBytes(32).toString('hex');
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
|
|
39
|
+
const codeVerifier = generateCodeVerifier();
|
|
40
|
+
const codeChallenge = generateCodeChallenge(codeVerifier);
|
|
41
|
+
|
|
42
|
+
const authParams = new URLSearchParams({
|
|
43
|
+
client_id: OAUTH_CLIENT_ID,
|
|
44
|
+
response_type: 'code',
|
|
45
|
+
redirect_uri: REDIRECT_URI,
|
|
46
|
+
scope: OAUTH_SCOPE.join(' '),
|
|
59
47
|
access_type: 'offline',
|
|
60
|
-
scope: OAUTH_SCOPE,
|
|
61
48
|
prompt: 'consent',
|
|
62
49
|
state,
|
|
50
|
+
code_challenge: codeChallenge,
|
|
51
|
+
code_challenge_method: 'S256',
|
|
63
52
|
});
|
|
64
53
|
|
|
65
|
-
const
|
|
66
|
-
code: string;
|
|
67
|
-
}>((resolve, reject) => {
|
|
68
|
-
const server = http.createServer(async (req, res) => {
|
|
69
|
-
try {
|
|
70
|
-
if (!req.url || req.url.indexOf('/oauth2callback') === -1) {
|
|
71
|
-
res.writeHead(HTTP_REDIRECT, { Location: SIGN_IN_FAILURE_URL });
|
|
72
|
-
res.end();
|
|
73
|
-
reject(new Error('Unexpected request: ' + req.url));
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const qs = new url.URL(req.url, `http://127.0.0.1:${port}`).searchParams;
|
|
78
|
-
|
|
79
|
-
if (qs.get('error')) {
|
|
80
|
-
res.writeHead(HTTP_REDIRECT, { Location: SIGN_IN_FAILURE_URL });
|
|
81
|
-
res.end();
|
|
82
|
-
reject(new Error(`Google OAuth error: ${qs.get('error')}. ${qs.get('error_description') || ''}`));
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (qs.get('state') !== state) {
|
|
87
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
88
|
-
res.end('<h1>State mismatch. Possible CSRF attack.</h1>');
|
|
89
|
-
reject(new Error('State mismatch. Possible CSRF attack.'));
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (qs.get('code')) {
|
|
94
|
-
res.writeHead(HTTP_REDIRECT, { Location: SIGN_IN_SUCCESS_URL });
|
|
95
|
-
res.end();
|
|
96
|
-
resolve({ code: qs.get('code')! });
|
|
97
|
-
} else {
|
|
98
|
-
res.writeHead(HTTP_REDIRECT, { Location: SIGN_IN_FAILURE_URL });
|
|
99
|
-
res.end();
|
|
100
|
-
reject(new Error('No authorization code received.'));
|
|
101
|
-
}
|
|
102
|
-
} catch (e) {
|
|
103
|
-
res.writeHead(HTTP_REDIRECT, { Location: SIGN_IN_FAILURE_URL });
|
|
104
|
-
res.end();
|
|
105
|
-
reject(e instanceof Error ? e : new Error(String(e)));
|
|
106
|
-
} finally {
|
|
107
|
-
server.close();
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
server.listen(port, '127.0.0.1', () => {});
|
|
112
|
-
server.on('error', reject);
|
|
113
|
-
});
|
|
54
|
+
const authUrl = `https://accounts.google.com/o/oauth2/auth?${authParams.toString()}`;
|
|
114
55
|
|
|
115
56
|
console.log('\nOpening browser for Google authentication...\n');
|
|
57
|
+
console.log('After signing in, copy the authorization code from the redirect page.\n');
|
|
116
58
|
console.log(`If the browser doesn't open, visit:\n${authUrl}\n`);
|
|
117
59
|
|
|
118
60
|
const childProcess = await open(authUrl);
|
|
@@ -121,109 +63,130 @@ export async function authenticateWithGoogle(): Promise<OAuthResult> {
|
|
|
121
63
|
console.log(`Please visit:\n${authUrl}\n`);
|
|
122
64
|
});
|
|
123
65
|
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
reject(new Error('Authentication cancelled by user.'));
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
try {
|
|
140
|
-
const { code } = await Promise.race([
|
|
141
|
-
loginCompletePromise,
|
|
142
|
-
timeoutPromise,
|
|
143
|
-
cancellationPromise,
|
|
144
|
-
]);
|
|
145
|
-
|
|
146
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
147
|
-
|
|
148
|
-
const { tokens } = await client.getToken({
|
|
66
|
+
const code = await promptForAuthCode();
|
|
67
|
+
|
|
68
|
+
const startTime = Date.now();
|
|
69
|
+
const tokenResponse = await fetch('https://oauth2.googleapis.com/token', {
|
|
70
|
+
method: 'POST',
|
|
71
|
+
headers: {
|
|
72
|
+
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
|
|
73
|
+
},
|
|
74
|
+
body: new URLSearchParams({
|
|
75
|
+
client_id: OAUTH_CLIENT_ID,
|
|
76
|
+
client_secret: OAUTH_CLIENT_SECRET,
|
|
149
77
|
code,
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const userInfo = await fetchUserInfo(client);
|
|
78
|
+
grant_type: 'authorization_code',
|
|
79
|
+
redirect_uri: REDIRECT_URI,
|
|
80
|
+
code_verifier: codeVerifier,
|
|
81
|
+
}),
|
|
82
|
+
});
|
|
156
83
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
refresh_token: tokens.refresh_token || '',
|
|
161
|
-
id_token: tokens.id_token || '',
|
|
162
|
-
scope: tokens.scope || OAUTH_SCOPE.join(' '),
|
|
163
|
-
token_type: tokens.token_type || 'Bearer',
|
|
164
|
-
expiry_date: tokens.expiry_date || Date.now(),
|
|
165
|
-
},
|
|
166
|
-
email: userInfo.email,
|
|
167
|
-
name: userInfo.name,
|
|
168
|
-
picture: userInfo.picture,
|
|
169
|
-
};
|
|
170
|
-
} finally {
|
|
171
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
84
|
+
if (!tokenResponse.ok) {
|
|
85
|
+
const errorText = await tokenResponse.text();
|
|
86
|
+
throw new Error(`Token exchange failed: ${tokenResponse.status} ${errorText}`);
|
|
172
87
|
}
|
|
173
|
-
}
|
|
174
88
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
89
|
+
const tokenPayload = (await tokenResponse.json()) as {
|
|
90
|
+
access_token: string;
|
|
91
|
+
refresh_token: string;
|
|
92
|
+
expires_in: number;
|
|
93
|
+
token_type: string;
|
|
94
|
+
scope: string;
|
|
95
|
+
};
|
|
182
96
|
|
|
183
|
-
const
|
|
184
|
-
headers: { Authorization: `Bearer ${
|
|
97
|
+
const userInfoResponse = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
|
|
98
|
+
headers: { Authorization: `Bearer ${tokenPayload.access_token}` },
|
|
185
99
|
});
|
|
186
100
|
|
|
187
|
-
if (!
|
|
188
|
-
throw new Error(`Failed to fetch user info: ${
|
|
101
|
+
if (!userInfoResponse.ok) {
|
|
102
|
+
throw new Error(`Failed to fetch user info: ${userInfoResponse.status} ${userInfoResponse.statusText}`);
|
|
189
103
|
}
|
|
190
104
|
|
|
191
|
-
const userInfo = (await
|
|
105
|
+
const userInfo = (await userInfoResponse.json()) as {
|
|
192
106
|
email: string;
|
|
193
107
|
name: string;
|
|
194
108
|
picture?: string;
|
|
195
109
|
};
|
|
196
110
|
|
|
197
111
|
return {
|
|
112
|
+
tokens: {
|
|
113
|
+
access_token: tokenPayload.access_token,
|
|
114
|
+
refresh_token: tokenPayload.refresh_token,
|
|
115
|
+
id_token: '',
|
|
116
|
+
scope: tokenPayload.scope || OAUTH_SCOPE.join(' '),
|
|
117
|
+
token_type: tokenPayload.token_type || 'Bearer',
|
|
118
|
+
expiry_date: startTime + tokenPayload.expires_in * 1000,
|
|
119
|
+
},
|
|
198
120
|
email: userInfo.email,
|
|
199
121
|
name: userInfo.name,
|
|
200
122
|
picture: userInfo.picture,
|
|
201
123
|
};
|
|
202
124
|
}
|
|
203
125
|
|
|
126
|
+
function promptForAuthCode(): Promise<string> {
|
|
127
|
+
return new Promise((resolve, reject) => {
|
|
128
|
+
const rl = readline.createInterface({
|
|
129
|
+
input: process.stdin,
|
|
130
|
+
output: process.stdout,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const authTimeout = 5 * 60 * 1000;
|
|
134
|
+
const timeoutId = setTimeout(() => {
|
|
135
|
+
rl.close();
|
|
136
|
+
reject(new Error('Authentication timed out after 5 minutes.'));
|
|
137
|
+
}, authTimeout);
|
|
138
|
+
|
|
139
|
+
const onSigint = () => {
|
|
140
|
+
clearTimeout(timeoutId);
|
|
141
|
+
rl.close();
|
|
142
|
+
reject(new Error('Authentication cancelled by user.'));
|
|
143
|
+
};
|
|
144
|
+
process.on('SIGINT', onSigint);
|
|
145
|
+
|
|
146
|
+
rl.question('Paste the authorization code: ', (answer) => {
|
|
147
|
+
clearTimeout(timeoutId);
|
|
148
|
+
process.removeListener('SIGINT', onSigint);
|
|
149
|
+
rl.close();
|
|
150
|
+
const code = answer.trim();
|
|
151
|
+
if (!code) {
|
|
152
|
+
reject(new Error('No authorization code provided.'));
|
|
153
|
+
} else {
|
|
154
|
+
resolve(code);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
204
160
|
export async function refreshTokens(tokens: TokenData): Promise<TokenData | null> {
|
|
205
161
|
try {
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
162
|
+
const tokenResponse = await fetch('https://oauth2.googleapis.com/token', {
|
|
163
|
+
method: 'POST',
|
|
164
|
+
headers: {
|
|
165
|
+
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
|
|
166
|
+
},
|
|
167
|
+
body: new URLSearchParams({
|
|
168
|
+
client_id: OAUTH_CLIENT_ID,
|
|
169
|
+
client_secret: OAUTH_CLIENT_SECRET,
|
|
170
|
+
refresh_token: tokens.refresh_token,
|
|
171
|
+
grant_type: 'refresh_token',
|
|
172
|
+
}),
|
|
209
173
|
});
|
|
210
174
|
|
|
211
|
-
|
|
212
|
-
access_token: tokens.access_token,
|
|
213
|
-
refresh_token: tokens.refresh_token,
|
|
214
|
-
});
|
|
175
|
+
if (!tokenResponse.ok) return null;
|
|
215
176
|
|
|
216
|
-
const
|
|
217
|
-
|
|
177
|
+
const tokenPayload = (await tokenResponse.json()) as {
|
|
178
|
+
access_token: string;
|
|
179
|
+
expires_in: number;
|
|
180
|
+
token_type: string;
|
|
181
|
+
};
|
|
218
182
|
|
|
219
|
-
const credentials = client.credentials;
|
|
220
183
|
return {
|
|
221
|
-
access_token:
|
|
222
|
-
refresh_token:
|
|
223
|
-
id_token:
|
|
184
|
+
access_token: tokenPayload.access_token,
|
|
185
|
+
refresh_token: tokens.refresh_token,
|
|
186
|
+
id_token: tokens.id_token || '',
|
|
224
187
|
scope: tokens.scope,
|
|
225
|
-
token_type:
|
|
226
|
-
expiry_date:
|
|
188
|
+
token_type: tokenPayload.token_type || 'Bearer',
|
|
189
|
+
expiry_date: Date.now() + tokenPayload.expires_in * 1000,
|
|
227
190
|
};
|
|
228
191
|
} catch {
|
|
229
192
|
return null;
|
package/src/commands/current.ts
CHANGED
|
@@ -7,8 +7,8 @@ export async function currentCommand(): Promise<void> {
|
|
|
7
7
|
|
|
8
8
|
if (!activeUsername || !accounts.accounts[activeUsername]) {
|
|
9
9
|
console.log('No active account.');
|
|
10
|
-
console.log('Use "
|
|
11
|
-
console.log('Use "
|
|
10
|
+
console.log('Use "gemix list" to see available accounts.');
|
|
11
|
+
console.log('Use "gemix use <username>" to switch accounts.');
|
|
12
12
|
return;
|
|
13
13
|
}
|
|
14
14
|
|
package/src/commands/list.ts
CHANGED
|
@@ -7,7 +7,7 @@ export async function listCommand(json: boolean = false): Promise<void> {
|
|
|
7
7
|
|
|
8
8
|
if (Object.keys(accounts.accounts).length === 0) {
|
|
9
9
|
console.log('No accounts registered.');
|
|
10
|
-
console.log('Run "
|
|
10
|
+
console.log('Run "gemix register <username>" to add an account.');
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
13
13
|
|
package/src/commands/register.ts
CHANGED
|
@@ -13,7 +13,7 @@ export async function registerCommand(username: string): Promise<void> {
|
|
|
13
13
|
const accounts = await loadAccounts();
|
|
14
14
|
if (accounts.accounts[username]) {
|
|
15
15
|
console.error(`Error: Account '${username}' already exists.`);
|
|
16
|
-
console.error(`Use '
|
|
16
|
+
console.error(`Use 'gemix remove ${username}' to remove it first.`);
|
|
17
17
|
process.exit(1);
|
|
18
18
|
}
|
|
19
19
|
|
package/src/commands/remove.ts
CHANGED
|
@@ -6,7 +6,7 @@ export async function removeCommand(username: string, yes: boolean = false): Pro
|
|
|
6
6
|
|
|
7
7
|
if (!accounts.accounts[username]) {
|
|
8
8
|
console.error(`Error: Account '${username}' not found.`);
|
|
9
|
-
console.error('Use "
|
|
9
|
+
console.error('Use "gemix list" to see available accounts.');
|
|
10
10
|
process.exit(1);
|
|
11
11
|
}
|
|
12
12
|
|
package/src/commands/use.ts
CHANGED
|
@@ -5,7 +5,7 @@ export async function useCommand(username: string): Promise<void> {
|
|
|
5
5
|
|
|
6
6
|
if (!accounts.accounts[username]) {
|
|
7
7
|
console.error(`Error: Account '${username}' not found.`);
|
|
8
|
-
console.error('Use "
|
|
8
|
+
console.error('Use "gemix list" to see available accounts.');
|
|
9
9
|
process.exit(1);
|
|
10
10
|
}
|
|
11
11
|
|
package/src/index.ts
CHANGED
package/src/storage/paths.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as path from 'node:path';
|
|
|
2
2
|
import * as os from 'node:os';
|
|
3
3
|
import { promises as fs } from 'node:fs';
|
|
4
4
|
|
|
5
|
-
const GEMX_DIR = '.
|
|
5
|
+
const GEMX_DIR = '.gemix';
|
|
6
6
|
const GEMINI_DIR = '.gemini';
|
|
7
7
|
const ACCOUNTS_FILE = 'accounts.json';
|
|
8
8
|
const ACTIVE_FILE = 'active';
|