@ian2018cs/agenthub 0.1.20 → 0.1.21
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/assets/{index-C6s2SqL8.js → index-CPhbi9pq.js} +47 -47
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/server/index.js +246 -1
- package/server/services/gemini-usage-scanner.js +249 -0
- package/server/services/pricing.js +46 -0
- package/server/services/usage-scanner.js +7 -0
- package/server/services/user-directories.js +101 -0
|
@@ -27,6 +27,7 @@ export function getUserPaths(userUuid) {
|
|
|
27
27
|
skillsImportDir: path.join(DATA_DIR, 'user-data', userUuid, 'skills-import'),
|
|
28
28
|
skillsRepoDir: path.join(DATA_DIR, 'user-data', userUuid, 'skills-repo'),
|
|
29
29
|
codexHomeDir: path.join(DATA_DIR, 'user-data', userUuid, 'codex-home'),
|
|
30
|
+
geminiHomeDir: path.join(DATA_DIR, 'user-data', userUuid, 'gemini-home'),
|
|
30
31
|
};
|
|
31
32
|
}
|
|
32
33
|
|
|
@@ -128,6 +129,103 @@ base_url = "${baseUrl}"
|
|
|
128
129
|
return codexDir;
|
|
129
130
|
}
|
|
130
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Initialize gemini home directory and skills for a user.
|
|
134
|
+
* Safe to call multiple times - only creates files that don't already exist.
|
|
135
|
+
* Note: skillsDir must exist before calling this function (created in initUserDirectories).
|
|
136
|
+
*/
|
|
137
|
+
export async function initGeminiDirectories(userUuid) {
|
|
138
|
+
validateUuid(userUuid);
|
|
139
|
+
const paths = getUserPaths(userUuid);
|
|
140
|
+
const geminiDir = path.join(paths.geminiHomeDir, '.gemini');
|
|
141
|
+
|
|
142
|
+
await fs.mkdir(geminiDir, { recursive: true });
|
|
143
|
+
|
|
144
|
+
// Write .env file (overwrite to keep in sync with server env vars)
|
|
145
|
+
// Gemini CLI reads GEMINI_API_KEY and GOOGLE_GEMINI_BASE_URL from GEMINI_CLI_HOME/.gemini/.env
|
|
146
|
+
const geminiApiKey = process.env.GEMINI_API_KEY || '';
|
|
147
|
+
const geminiBaseUrl = process.env.GOOGLE_GEMINI_BASE_URL || '';
|
|
148
|
+
let envContent = '';
|
|
149
|
+
if (geminiApiKey) envContent += `GEMINI_API_KEY=${geminiApiKey}\n`;
|
|
150
|
+
if (geminiBaseUrl) envContent += `GOOGLE_GEMINI_BASE_URL=${geminiBaseUrl}\n`;
|
|
151
|
+
await fs.writeFile(path.join(geminiDir, '.env'), envContent, 'utf8');
|
|
152
|
+
|
|
153
|
+
// Ensure settings.json exists and has selectedType set
|
|
154
|
+
const settingsJsonPath = path.join(geminiDir, 'settings.json');
|
|
155
|
+
let geminiSettings = {};
|
|
156
|
+
try {
|
|
157
|
+
const settingsContent = await fs.readFile(settingsJsonPath, 'utf8');
|
|
158
|
+
geminiSettings = JSON.parse(settingsContent);
|
|
159
|
+
} catch {
|
|
160
|
+
// File doesn't exist or is invalid JSON, start fresh
|
|
161
|
+
}
|
|
162
|
+
if (!geminiSettings?.security?.auth?.selectedType) {
|
|
163
|
+
geminiSettings = {
|
|
164
|
+
...geminiSettings,
|
|
165
|
+
security: {
|
|
166
|
+
...(geminiSettings.security || {}),
|
|
167
|
+
auth: {
|
|
168
|
+
...(geminiSettings.security?.auth || {}),
|
|
169
|
+
selectedType: 'gemini-api-key'
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
await fs.writeFile(settingsJsonPath, JSON.stringify(geminiSettings, null, 2), 'utf8');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Sync skills symlinks into .gemini/skills/:
|
|
177
|
+
// Mirror each enabled skill from .claude/skills/ into .gemini/skills/<skill> (symlink).
|
|
178
|
+
const geminiSkillsDir = path.join(geminiDir, 'skills');
|
|
179
|
+
await fs.mkdir(geminiSkillsDir, { recursive: true });
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
// Add symlinks for newly enabled skills
|
|
183
|
+
let claudeSkills = [];
|
|
184
|
+
try {
|
|
185
|
+
claudeSkills = await fs.readdir(paths.skillsDir);
|
|
186
|
+
} catch {
|
|
187
|
+
// skillsDir doesn't exist yet - nothing to sync
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
for (const skillName of claudeSkills) {
|
|
191
|
+
const claudeSkillPath = path.join(paths.skillsDir, skillName);
|
|
192
|
+
const geminiSkillLink = path.join(geminiSkillsDir, skillName);
|
|
193
|
+
try {
|
|
194
|
+
await fs.lstat(geminiSkillLink);
|
|
195
|
+
// Already exists - skip
|
|
196
|
+
} catch {
|
|
197
|
+
await fs.symlink(claudeSkillPath, geminiSkillLink);
|
|
198
|
+
console.log(`[Gemini] Synced skill: ${skillName}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Remove stale symlinks (skills removed from .claude/skills/)
|
|
203
|
+
const geminiSkills = await fs.readdir(geminiSkillsDir);
|
|
204
|
+
for (const skillName of geminiSkills) {
|
|
205
|
+
if (skillName.startsWith('.')) continue; // preserve hidden dirs
|
|
206
|
+
const geminiSkillLink = path.join(geminiSkillsDir, skillName);
|
|
207
|
+
const claudeSkillPath = path.join(paths.skillsDir, skillName);
|
|
208
|
+
try {
|
|
209
|
+
await fs.access(claudeSkillPath);
|
|
210
|
+
} catch {
|
|
211
|
+
// No longer in .claude/skills/ - remove if it's a symlink
|
|
212
|
+
try {
|
|
213
|
+
const stat = await fs.lstat(geminiSkillLink);
|
|
214
|
+
if (stat.isSymbolicLink()) {
|
|
215
|
+
await fs.unlink(geminiSkillLink);
|
|
216
|
+
console.log(`[Gemini] Removed stale skill symlink: ${skillName}`);
|
|
217
|
+
}
|
|
218
|
+
} catch {}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
} catch (err) {
|
|
222
|
+
console.log(`[Gemini] Skills sync warning: ${err.message}`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
console.log(`[Gemini] Initialized gemini directories for user ${userUuid}`);
|
|
226
|
+
return geminiDir;
|
|
227
|
+
}
|
|
228
|
+
|
|
131
229
|
/**
|
|
132
230
|
* Initialize directories for a new user
|
|
133
231
|
*/
|
|
@@ -151,6 +249,9 @@ export async function initUserDirectories(userUuid) {
|
|
|
151
249
|
// Initialize codex home directory with config files (after skillsDir exists for symlink)
|
|
152
250
|
await initCodexDirectories(userUuid);
|
|
153
251
|
|
|
252
|
+
// Initialize gemini home directory with skills symlinks
|
|
253
|
+
await initGeminiDirectories(userUuid);
|
|
254
|
+
|
|
154
255
|
// Create .claude.json with hasCompletedOnboarding=true
|
|
155
256
|
const claudeJsonPath = path.join(paths.claudeDir, '.claude.json');
|
|
156
257
|
const claudeConfig = {
|