opencode-studio-server 1.3.8 → 1.4.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 (2) hide show
  1. package/index.js +132 -8
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -7,6 +7,9 @@ const os = require('os');
7
7
  const crypto = require('crypto');
8
8
  const { spawn, exec } = require('child_process');
9
9
 
10
+ const pkg = require('./package.json');
11
+ const SERVER_VERSION = pkg.version;
12
+
10
13
  // Atomic file write: write to temp file then rename to prevent corruption
11
14
  const atomicWriteFileSync = (filePath, data, options = 'utf8') => {
12
15
  const dir = path.dirname(filePath);
@@ -221,6 +224,7 @@ function processLogLine(line) {
221
224
 
222
225
  // Start watcher on server start
223
226
  setupLogWatcher();
227
+ importExistingAuth();
224
228
 
225
229
  let pendingActionMemory = null;
226
230
 
@@ -435,7 +439,7 @@ const saveConfig = (config) => {
435
439
  atomicWriteFileSync(configPath, JSON.stringify(config, null, 2));
436
440
  };
437
441
 
438
- app.get('/api/health', (req, res) => res.json({ status: 'ok' }));
442
+ app.get('/api/health', (req, res) => res.json({ status: 'ok', version: SERVER_VERSION }));
439
443
 
440
444
  app.post('/api/shutdown', (req, res) => {
441
445
  res.json({ success: true });
@@ -444,6 +448,56 @@ app.post('/api/shutdown', (req, res) => {
444
448
 
445
449
  app.get('/api/paths', (req, res) => res.json(getPaths()));
446
450
 
451
+ app.get('/api/debug/auth', (req, res) => {
452
+ const paths = getPaths();
453
+ const studio = loadStudioConfig();
454
+ const activePlugin = studio.activeGooglePlugin;
455
+
456
+ // Check auth.json in all candidate locations
457
+ const authLocations = [];
458
+ paths.candidates.forEach(p => {
459
+ const ap = path.join(path.dirname(p), 'auth.json');
460
+ authLocations.push({
461
+ path: ap,
462
+ exists: fs.existsSync(ap),
463
+ keys: fs.existsSync(ap) ? Object.keys(JSON.parse(fs.readFileSync(ap, 'utf8'))) : []
464
+ });
465
+ });
466
+
467
+ // Check current auth.json
468
+ if (paths.current) {
469
+ const ap = path.join(path.dirname(paths.current), 'auth.json');
470
+ if (!authLocations.some(l => l.path === ap)) {
471
+ authLocations.push({
472
+ path: ap,
473
+ exists: fs.existsSync(ap),
474
+ keys: fs.existsSync(ap) ? Object.keys(JSON.parse(fs.readFileSync(ap, 'utf8'))) : []
475
+ });
476
+ }
477
+ }
478
+
479
+ // Check profile directories
480
+ const namespaces = ['google', 'google.gemini', 'google.antigravity', 'openai', 'anthropic'];
481
+ const profileDirs = {};
482
+ namespaces.forEach(ns => {
483
+ const dir = path.join(AUTH_PROFILES_DIR, ns);
484
+ profileDirs[ns] = {
485
+ path: dir,
486
+ exists: fs.existsSync(dir),
487
+ profiles: fs.existsSync(dir) ? fs.readdirSync(dir).filter(f => f.endsWith('.json')) : []
488
+ };
489
+ });
490
+
491
+ res.json({
492
+ configPath: paths.current,
493
+ activeGooglePlugin: activePlugin,
494
+ activeProfiles: studio.activeProfiles || {},
495
+ authLocations,
496
+ profileDirs,
497
+ authProfilesDir: AUTH_PROFILES_DIR
498
+ });
499
+ });
500
+
447
501
  app.post('/api/paths', (req, res) => {
448
502
  const { configPath } = req.body;
449
503
  const studioConfig = loadStudioConfig();
@@ -475,9 +529,11 @@ const getSkillDir = () => {
475
529
  app.get('/api/skills', (req, res) => {
476
530
  const sd = getSkillDir();
477
531
  if (!sd || !fs.existsSync(sd)) return res.json([]);
532
+ const studio = loadStudioConfig();
533
+ const disabledSkills = studio.disabledSkills || [];
478
534
  const skills = fs.readdirSync(sd, { withFileTypes: true })
479
535
  .filter(e => e.isDirectory() && fs.existsSync(path.join(sd, e.name, 'SKILL.md')))
480
- .map(e => ({ name: e.name, path: path.join(sd, e.name, 'SKILL.md'), enabled: !e.name.endsWith('.disabled') }));
536
+ .map(e => ({ name: e.name, path: path.join(sd, e.name, 'SKILL.md'), enabled: !disabledSkills.includes(e.name) }));
481
537
  res.json(skills);
482
538
  });
483
539
 
@@ -537,9 +593,13 @@ app.get('/api/plugins', (req, res) => {
537
593
  const pd = getPluginDir();
538
594
  const cp = getConfigPath();
539
595
  const cr = cp ? path.dirname(cp) : null;
596
+ const studio = loadStudioConfig();
597
+ const disabledPlugins = studio.disabledPlugins || [];
540
598
  const plugins = [];
541
- const add = (name, p, enabled = true) => {
542
- if (!plugins.some(pl => pl.name === name)) plugins.push({ name, path: p, enabled });
599
+ const add = (name, p, type = 'file') => {
600
+ if (!plugins.some(pl => pl.name === name)) {
601
+ plugins.push({ name, path: p, type, enabled: !disabledPlugins.includes(name) });
602
+ }
543
603
  };
544
604
 
545
605
  if (pd && fs.existsSync(pd)) {
@@ -548,9 +608,9 @@ app.get('/api/plugins', (req, res) => {
548
608
  const st = fs.lstatSync(fp);
549
609
  if (st.isDirectory()) {
550
610
  const j = path.join(fp, 'index.js'), t = path.join(fp, 'index.ts');
551
- if (fs.existsSync(j) || fs.existsSync(t)) add(e.name, fs.existsSync(j) ? j : t);
611
+ if (fs.existsSync(j) || fs.existsSync(t)) add(e.name, fs.existsSync(j) ? j : t, 'file');
552
612
  } else if ((st.isFile() || st.isSymbolicLink()) && /\.(js|ts)$/.test(e.name)) {
553
- add(e.name.replace(/\.(js|ts)$/, ''), fp);
613
+ add(e.name.replace(/\.(js|ts)$/, ''), fp, 'file');
554
614
  }
555
615
  });
556
616
  }
@@ -558,14 +618,14 @@ app.get('/api/plugins', (req, res) => {
558
618
  if (cr && fs.existsSync(cr)) {
559
619
  ['oh-my-opencode', 'superpowers', 'opencode-gemini-auth'].forEach(n => {
560
620
  const fp = path.join(cr, n);
561
- if (fs.existsSync(fp) && fs.statSync(fp).isDirectory()) add(n, fp);
621
+ if (fs.existsSync(fp) && fs.statSync(fp).isDirectory()) add(n, fp, 'file');
562
622
  });
563
623
  }
564
624
 
565
625
  const cfg = loadConfig();
566
626
  if (cfg && Array.isArray(cfg.plugin)) {
567
627
  cfg.plugin.forEach(n => {
568
- if (!n.includes('/') && !n.includes('\\') && !/\.(js|ts)$/.test(n)) add(n, 'npm');
628
+ if (!n.includes('/') && !n.includes('\\') && !/\.(js|ts)$/.test(n)) add(n, 'npm', 'npm');
569
629
  });
570
630
  }
571
631
  res.json(plugins);
@@ -1192,6 +1252,70 @@ function syncAntigravityPool() {
1192
1252
  savePoolMetadata(metadata);
1193
1253
  }
1194
1254
 
1255
+ function importExistingAuth() {
1256
+ const authCfg = loadAuthConfig();
1257
+ if (!authCfg) return;
1258
+
1259
+ const studio = loadStudioConfig();
1260
+ const activeProfiles = studio.activeProfiles || {};
1261
+ let changed = false;
1262
+
1263
+ // Standard providers to check
1264
+ const providers = ['google', 'openai', 'anthropic', 'xai', 'openrouter', 'github-copilot', 'mistral', 'deepseek', 'amazon-bedrock', 'azure'];
1265
+
1266
+ providers.forEach(provider => {
1267
+ if (!authCfg[provider]) return; // No creds for this provider
1268
+
1269
+ // Determine namespace
1270
+ // For auto-import, we target the standard 'google' namespace unless antigravity plugin is active?
1271
+ // Actually, auth.json 'google' key usually means Gemini/Vertex standard auth.
1272
+ const namespace = provider === 'google' && studio.activeGooglePlugin === 'antigravity'
1273
+ ? 'google.antigravity'
1274
+ : (provider === 'google' ? 'google.gemini' : provider);
1275
+
1276
+ const profileDir = path.join(AUTH_PROFILES_DIR, namespace);
1277
+
1278
+ // If we already have an active profile for this provider, skip import
1279
+ if (activeProfiles[provider]) return;
1280
+
1281
+ // If directory exists and has files, check if empty
1282
+ if (fs.existsSync(profileDir) && fs.readdirSync(profileDir).filter(f => f.endsWith('.json')).length > 0) {
1283
+ return;
1284
+ }
1285
+
1286
+ // Import!
1287
+ if (!fs.existsSync(profileDir)) fs.mkdirSync(profileDir, { recursive: true });
1288
+
1289
+ const email = authCfg[provider].email || null;
1290
+ const name = email || `imported-${Date.now()}`;
1291
+ const profilePath = path.join(profileDir, `${name}.json`);
1292
+
1293
+ console.log(`[AutoImport] Importing existing ${provider} credentials as ${name}`);
1294
+ atomicWriteFileSync(profilePath, JSON.stringify(authCfg[provider], null, 2));
1295
+
1296
+ // Set as active
1297
+ if (!studio.activeProfiles) studio.activeProfiles = {};
1298
+ studio.activeProfiles[provider] = name;
1299
+ changed = true;
1300
+
1301
+ // Update metadata
1302
+ const metadata = loadPoolMetadata();
1303
+ if (!metadata[namespace]) metadata[namespace] = {};
1304
+ metadata[namespace][name] = {
1305
+ email: email,
1306
+ createdAt: Date.now(),
1307
+ lastUsed: Date.now(),
1308
+ usageCount: 0,
1309
+ imported: true
1310
+ };
1311
+ savePoolMetadata(metadata);
1312
+ });
1313
+
1314
+ if (changed) {
1315
+ saveStudioConfig(studio);
1316
+ }
1317
+ }
1318
+
1195
1319
  function getAccountStatus(meta, now) {
1196
1320
  if (!meta) return 'ready';
1197
1321
  if (meta.cooldownUntil && meta.cooldownUntil > now) return 'cooldown';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-studio-server",
3
- "version": "1.3.8",
3
+ "version": "1.4.0",
4
4
  "description": "Backend server for OpenCode Studio - manages opencode configurations",
5
5
  "main": "index.js",
6
6
  "bin": {