@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.
@@ -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 = {