gemix 0.0.1 → 0.1.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.
Files changed (48) hide show
  1. package/README.md +101 -0
  2. package/dist/auth/oauth.d.ts +13 -0
  3. package/dist/auth/oauth.d.ts.map +1 -1
  4. package/dist/auth/oauth.js +165 -140
  5. package/dist/auth/oauth.js.map +1 -1
  6. package/dist/commands/current.js +2 -2
  7. package/dist/commands/current.js.map +1 -1
  8. package/dist/commands/dash.d.ts +2 -0
  9. package/dist/commands/dash.d.ts.map +1 -0
  10. package/dist/commands/dash.js +125 -0
  11. package/dist/commands/dash.js.map +1 -0
  12. package/dist/commands/list.js +1 -1
  13. package/dist/commands/list.js.map +1 -1
  14. package/dist/commands/register.js +1 -1
  15. package/dist/commands/register.js.map +1 -1
  16. package/dist/commands/remove.js +1 -1
  17. package/dist/commands/remove.js.map +1 -1
  18. package/dist/commands/use.js +1 -1
  19. package/dist/commands/use.js.map +1 -1
  20. package/dist/index.js +16 -2
  21. package/dist/index.js.map +1 -1
  22. package/dist/server/public/app.js +181 -0
  23. package/dist/server/public/index.html +77 -0
  24. package/dist/server/public/style.css +132 -0
  25. package/dist/server/quota.d.ts +22 -0
  26. package/dist/server/quota.d.ts.map +1 -0
  27. package/dist/server/quota.js +69 -0
  28. package/dist/server/quota.js.map +1 -0
  29. package/dist/server/server.d.ts +11 -0
  30. package/dist/server/server.d.ts.map +1 -0
  31. package/dist/server/server.js +170 -0
  32. package/dist/server/server.js.map +1 -0
  33. package/dist/storage/paths.js +1 -1
  34. package/dist/storage/paths.js.map +1 -1
  35. package/package.json +20 -9
  36. package/doc/README.md +0 -90
  37. package/src/auth/oauth.ts +0 -231
  38. package/src/commands/current.ts +0 -18
  39. package/src/commands/list.ts +0 -20
  40. package/src/commands/register.ts +0 -53
  41. package/src/commands/remove.ts +0 -37
  42. package/src/commands/use.ts +0 -25
  43. package/src/index.ts +0 -59
  44. package/src/storage/accounts.ts +0 -285
  45. package/src/storage/paths.ts +0 -56
  46. package/src/utils/table.ts +0 -58
  47. package/src/utils/validation.ts +0 -27
  48. package/tsconfig.json +0 -20
@@ -1,285 +0,0 @@
1
- import { promises as fs } from 'node:fs';
2
- import { execSync } from 'node:child_process';
3
- import {
4
- getAccountsFilePath,
5
- getActiveFilePath,
6
- getAccountTokensPath,
7
- getAccountDir,
8
- ensureGemxDir,
9
- ensureAccountDir,
10
- getGeminiOAuthCredsPath,
11
- getGeminiGoogleAccountsPath,
12
- getAntigravityOAuthTokenPath,
13
- } from './paths.js';
14
- import { refreshTokens } from '../auth/oauth.js';
15
-
16
- export interface AccountLimits {
17
- requestsPerDay: number;
18
- tokensPerDay: number;
19
- }
20
-
21
- export interface AccountInfo {
22
- email: string;
23
- name: string;
24
- picture?: string;
25
- limits: AccountLimits;
26
- created: string;
27
- lastUsed: string | null;
28
- }
29
-
30
- export interface AccountsData {
31
- accounts: Record<string, AccountInfo>;
32
- active: string | null;
33
- }
34
-
35
- export interface TokenData {
36
- access_token: string;
37
- refresh_token: string;
38
- id_token?: string;
39
- scope: string;
40
- token_type: string;
41
- expiry_date: number;
42
- }
43
-
44
- export interface GoogleAccountsData {
45
- active: string;
46
- old: string[];
47
- }
48
-
49
- const DEFAULT_LIMITS: AccountLimits = {
50
- requestsPerDay: 500,
51
- tokensPerDay: 1000000,
52
- };
53
-
54
- export async function loadAccounts(): Promise<AccountsData> {
55
- await ensureGemxDir();
56
- const filePath = getAccountsFilePath();
57
- try {
58
- const content = await fs.readFile(filePath, 'utf-8');
59
- return JSON.parse(content) as AccountsData;
60
- } catch {
61
- return { accounts: {}, active: null };
62
- }
63
- }
64
-
65
- export async function saveAccounts(data: AccountsData): Promise<void> {
66
- await ensureGemxDir();
67
- const filePath = getAccountsFilePath();
68
- await fs.writeFile(filePath, JSON.stringify(data, null, 2), { mode: 0o600 });
69
- }
70
-
71
- export async function loadTokens(username: string): Promise<TokenData | null> {
72
- const tokensPath = getAccountTokensPath(username);
73
- try {
74
- const content = await fs.readFile(tokensPath, 'utf-8');
75
- return JSON.parse(content) as TokenData;
76
- } catch {
77
- return null;
78
- }
79
- }
80
-
81
- export async function saveTokens(username: string, tokens: TokenData): Promise<void> {
82
- await ensureAccountDir(username);
83
- const tokensPath = getAccountTokensPath(username);
84
- await fs.writeFile(tokensPath, JSON.stringify(tokens, null, 2), { mode: 0o600 });
85
- }
86
-
87
- export async function loadGoogleAccounts(): Promise<GoogleAccountsData> {
88
- const filePath = getGeminiGoogleAccountsPath();
89
- try {
90
- const content = await fs.readFile(filePath, 'utf-8');
91
- return JSON.parse(content) as GoogleAccountsData;
92
- } catch {
93
- return { active: '', old: [] };
94
- }
95
- }
96
-
97
- export async function saveGoogleAccounts(data: GoogleAccountsData): Promise<void> {
98
- const filePath = getGeminiGoogleAccountsPath();
99
- const dir = filePath.substring(0, filePath.lastIndexOf('/'));
100
- await fs.mkdir(dir, { recursive: true });
101
- await fs.writeFile(filePath, JSON.stringify(data, null, 2), { mode: 0o600 });
102
- }
103
-
104
- export async function updateGoogleAccountsActive(email: string): Promise<void> {
105
- const data = await loadGoogleAccounts();
106
-
107
- if (data.active && data.active !== email) {
108
- data.old = [...(data.old || []), data.active];
109
- }
110
-
111
- data.active = email;
112
- await saveGoogleAccounts(data);
113
- }
114
-
115
- export async function copyTokensToAntigravity(tokens: TokenData): Promise<boolean> {
116
- try {
117
- const antigravityPath = getAntigravityOAuthTokenPath();
118
- const dir = antigravityPath.substring(0, antigravityPath.lastIndexOf('/'));
119
- await fs.mkdir(dir, { recursive: true });
120
-
121
- const antigravityData = {
122
- token: {
123
- access_token: tokens.access_token,
124
- token_type: tokens.token_type,
125
- refresh_token: tokens.refresh_token,
126
- expiry: new Date(tokens.expiry_date).toISOString(),
127
- },
128
- auth_method: 'consumer',
129
- };
130
-
131
- await fs.writeFile(antigravityPath, JSON.stringify(antigravityData, null, 2), { mode: 0o600 });
132
- return true;
133
- } catch {
134
- return false;
135
- }
136
- }
137
-
138
- export async function copyTokensToKeychain(tokens: TokenData): Promise<boolean> {
139
- try {
140
- const keychainData = {
141
- token: {
142
- access_token: tokens.access_token,
143
- token_type: tokens.token_type,
144
- refresh_token: tokens.refresh_token,
145
- expiry: new Date(tokens.expiry_date).toISOString(),
146
- },
147
- auth_method: 'consumer',
148
- };
149
-
150
- const password = `go-keyring-base64:${Buffer.from(JSON.stringify(keychainData)).toString('base64')}`;
151
-
152
- try {
153
- execSync(`security delete-generic-password -s "gemini" -a "antigravity" 2>/dev/null`, { stdio: 'ignore' });
154
- } catch {
155
- // Ignore if entry doesn't exist
156
- }
157
-
158
- execSync(`security add-generic-password -s "gemini" -a "antigravity" -w "${password}" -U`, { stdio: 'ignore' });
159
- return true;
160
- } catch {
161
- return false;
162
- }
163
- }
164
-
165
- export async function getActiveUsername(): Promise<string | null> {
166
- const activePath = getActiveFilePath();
167
- try {
168
- return await fs.readFile(activePath, 'utf-8');
169
- } catch {
170
- return null;
171
- }
172
- }
173
-
174
- export async function setActiveUsername(username: string): Promise<void> {
175
- await ensureGemxDir();
176
- const activePath = getActiveFilePath();
177
- await fs.writeFile(activePath, username, { mode: 0o600 });
178
- }
179
-
180
- export async function copyTokensToGemini(username: string): Promise<boolean> {
181
- const tokens = await loadTokens(username);
182
- if (!tokens) return false;
183
-
184
- const geminiPath = getGeminiOAuthCredsPath();
185
- const geminiDir = geminiPath.substring(0, geminiPath.lastIndexOf('/'));
186
- await fs.mkdir(geminiDir, { recursive: true });
187
- await fs.writeFile(geminiPath, JSON.stringify(buildGeminiCredsObject(tokens), null, 2), { mode: 0o600 });
188
- return true;
189
- }
190
-
191
- export async function addAccount(
192
- username: string,
193
- email: string,
194
- name: string,
195
- picture: string | undefined,
196
- tokens: TokenData,
197
- limits: AccountLimits = DEFAULT_LIMITS
198
- ): Promise<void> {
199
- const accounts = await loadAccounts();
200
-
201
- accounts.accounts[username] = {
202
- email,
203
- name,
204
- picture,
205
- limits,
206
- created: new Date().toISOString(),
207
- lastUsed: null,
208
- };
209
-
210
- await saveTokens(username, tokens);
211
- await saveAccounts(accounts);
212
-
213
- if (!accounts.active) {
214
- await setActiveUsername(username);
215
- accounts.active = username;
216
- await saveAccounts(accounts);
217
- await updateGoogleAccountsActive(email);
218
- }
219
- }
220
-
221
- export async function removeAccount(username: string): Promise<boolean> {
222
- const accounts = await loadAccounts();
223
- if (!accounts.accounts[username]) return false;
224
-
225
- delete accounts.accounts[username];
226
-
227
- if (accounts.active === username) {
228
- const remaining = Object.keys(accounts.accounts);
229
- accounts.active = remaining.length > 0 ? remaining[0] : null;
230
- }
231
-
232
- await fs.rm(getAccountDir(username), { recursive: true, force: true });
233
- await saveAccounts(accounts);
234
- return true;
235
- }
236
-
237
- export function buildGeminiCredsObject(tokens: TokenData): Record<string, unknown> {
238
- const creds: Record<string, unknown> = {
239
- access_token: tokens.access_token,
240
- refresh_token: tokens.refresh_token,
241
- scope: tokens.scope,
242
- token_type: tokens.token_type,
243
- expiry_date: tokens.expiry_date,
244
- };
245
- if (tokens.id_token) {
246
- creds.id_token = tokens.id_token;
247
- }
248
- return creds;
249
- }
250
-
251
- export async function switchAccount(username: string): Promise<boolean> {
252
- const accounts = await loadAccounts();
253
- if (!accounts.accounts[username]) return false;
254
-
255
- let tokens = await loadTokens(username);
256
- if (!tokens) return false;
257
-
258
- // Refresh tokens if expired
259
- const now = Date.now();
260
- const fiveMinutes = 5 * 60 * 1000;
261
- if (tokens.expiry_date && tokens.expiry_date - now < fiveMinutes) {
262
- const refreshed = await refreshTokens(tokens);
263
- if (refreshed) {
264
- tokens = refreshed;
265
- await saveTokens(username, tokens);
266
- }
267
- }
268
-
269
- await fs.mkdir(getGeminiOAuthCredsPath().substring(0, getGeminiOAuthCredsPath().lastIndexOf('/')), { recursive: true });
270
- await fs.writeFile(getGeminiOAuthCredsPath(), JSON.stringify(buildGeminiCredsObject(tokens), null, 2), { mode: 0o600 });
271
- await copyTokensToAntigravity(tokens);
272
- await copyTokensToKeychain(tokens);
273
-
274
- await updateGoogleAccountsActive(accounts.accounts[username].email);
275
-
276
- accounts.active = username;
277
- accounts.accounts[username].lastUsed = new Date().toISOString();
278
- await saveAccounts(accounts);
279
- await setActiveUsername(username);
280
- return true;
281
- }
282
-
283
- export function validateUsername(username: string): boolean {
284
- return /^[a-zA-Z0-9]([a-zA-Z0-9-]{1,28}[a-zA-Z0-9])?$/.test(username);
285
- }
@@ -1,56 +0,0 @@
1
- import * as path from 'node:path';
2
- import * as os from 'node:os';
3
- import { promises as fs } from 'node:fs';
4
-
5
- const GEMX_DIR = '.gemx';
6
- const GEMINI_DIR = '.gemini';
7
- const ACCOUNTS_FILE = 'accounts.json';
8
- const ACTIVE_FILE = 'active';
9
- const ACCOUNTS_DIR = 'accounts';
10
- const OAUTH_CREDS_FILE = 'oauth_creds.json';
11
- const GOOGLE_ACCOUNTS_FILE = 'google_accounts.json';
12
-
13
- export function getGemxDir(): string {
14
- return path.join(os.homedir(), GEMX_DIR);
15
- }
16
-
17
- export function getAccountsFilePath(): string {
18
- return path.join(getGemxDir(), ACCOUNTS_FILE);
19
- }
20
-
21
- export function getActiveFilePath(): string {
22
- return path.join(getGemxDir(), ACTIVE_FILE);
23
- }
24
-
25
- export function getAccountsDir(): string {
26
- return path.join(getGemxDir(), ACCOUNTS_DIR);
27
- }
28
-
29
- export function getAccountDir(username: string): string {
30
- return path.join(getAccountsDir(), username);
31
- }
32
-
33
- export function getAccountTokensPath(username: string): string {
34
- return path.join(getAccountDir(username), 'tokens.json');
35
- }
36
-
37
- export function getGeminiOAuthCredsPath(): string {
38
- return path.join(os.homedir(), GEMINI_DIR, OAUTH_CREDS_FILE);
39
- }
40
-
41
- export function getGeminiGoogleAccountsPath(): string {
42
- return path.join(os.homedir(), GEMINI_DIR, GOOGLE_ACCOUNTS_FILE);
43
- }
44
-
45
- export function getAntigravityOAuthTokenPath(): string {
46
- return path.join(os.homedir(), GEMINI_DIR, 'antigravity-cli', 'antigravity-oauth-token');
47
- }
48
-
49
- export async function ensureGemxDir(): Promise<void> {
50
- await fs.mkdir(getGemxDir(), { recursive: true });
51
- await fs.mkdir(getAccountsDir(), { recursive: true });
52
- }
53
-
54
- export async function ensureAccountDir(username: string): Promise<void> {
55
- await fs.mkdir(getAccountDir(username), { recursive: true });
56
- }
@@ -1,58 +0,0 @@
1
- import Table from 'cli-table3';
2
- import type { AccountInfo } from '../storage/accounts.js';
3
-
4
- interface AccountRow {
5
- username: string;
6
- email: string;
7
- limits: string;
8
- status: string;
9
- }
10
-
11
- export function formatAccountTable(
12
- accounts: Record<string, AccountInfo>,
13
- activeUsername: string | null
14
- ): string {
15
- const table = new Table({
16
- head: ['USERNAME', 'EMAIL', 'LIMITS', 'STATUS'],
17
- style: { head: ['cyan'] },
18
- });
19
-
20
- const sortedUsernames = Object.keys(accounts).sort();
21
-
22
- for (const username of sortedUsernames) {
23
- const account = accounts[username];
24
- const isActive = username === activeUsername;
25
- const limits = `${account.limits.requestsPerDay} req/day`;
26
- const status = isActive ? '● active' : '○ inactive';
27
-
28
- table.push([username, account.email, limits, status]);
29
- }
30
-
31
- return table.toString();
32
- }
33
-
34
- export function formatAccountTableJson(
35
- accounts: Record<string, AccountInfo>,
36
- activeUsername: string | null
37
- ): object {
38
- const sortedUsernames = Object.keys(accounts).sort();
39
- const result = sortedUsernames.map((username) => ({
40
- username,
41
- email: accounts[username].email,
42
- name: accounts[username].name,
43
- limits: accounts[username].limits,
44
- active: username === activeUsername,
45
- created: accounts[username].created,
46
- lastUsed: accounts[username].lastUsed,
47
- }));
48
-
49
- return { accounts: result };
50
- }
51
-
52
- export function formatLimits(limits: { requestsPerDay: number; tokensPerDay: number }): string {
53
- const reqStr = `${limits.requestsPerDay} requests/day`;
54
- const tokenStr = limits.tokensPerDay >= 1000000
55
- ? `${(limits.tokensPerDay / 1000000).toFixed(0)}M tokens/day`
56
- : `${limits.tokensPerDay.toLocaleString()} tokens/day`;
57
- return `${reqStr}, ${tokenStr}`;
58
- }
@@ -1,27 +0,0 @@
1
- export function validateUsername(username: string): { valid: boolean; error?: string } {
2
- if (!username || username.length < 3) {
3
- return { valid: false, error: 'Username must be at least 3 characters' };
4
- }
5
-
6
- if (username.length > 30) {
7
- return { valid: false, error: 'Username must be at most 30 characters' };
8
- }
9
-
10
- if (!/^[a-zA-Z0-9]/.test(username)) {
11
- return { valid: false, error: 'Username must start with a letter or number' };
12
- }
13
-
14
- if (!/[a-zA-Z0-9]$/.test(username)) {
15
- return { valid: false, error: 'Username must end with a letter or number' };
16
- }
17
-
18
- if (!/^[a-zA-Z0-9-]+$/.test(username)) {
19
- return { valid: false, error: 'Username can only contain letters, numbers, and hyphens' };
20
- }
21
-
22
- if (username.includes('--')) {
23
- return { valid: false, error: 'Username cannot contain consecutive hyphens' };
24
- }
25
-
26
- return { valid: true };
27
- }
package/tsconfig.json DELETED
@@ -1,20 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "Node16",
5
- "moduleResolution": "Node16",
6
- "lib": ["ES2022"],
7
- "outDir": "./dist",
8
- "rootDir": "./src",
9
- "strict": true,
10
- "esModuleInterop": true,
11
- "skipLibCheck": true,
12
- "forceConsistentCasingInFileNames": true,
13
- "resolveJsonModule": true,
14
- "declaration": true,
15
- "declarationMap": true,
16
- "sourceMap": true
17
- },
18
- "include": ["src/**/*"],
19
- "exclude": ["node_modules", "dist"]
20
- }