opencode-studio-server 1.16.2 → 1.16.4

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 (3) hide show
  1. package/index.js +199 -11
  2. package/package.json +1 -1
  3. package/server.log +1 -0
package/index.js CHANGED
@@ -84,6 +84,7 @@ app.use(cors({
84
84
  }));
85
85
  app.use(bodyParser.json({ limit: '50mb' }));
86
86
  app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
87
+ app.use(bodyParser.text({ type: ['text/*', 'application/yaml'], limit: '50mb' }));
87
88
 
88
89
  const HOME_DIR = os.homedir();
89
90
  const STUDIO_CONFIG_PATH = path.join(HOME_DIR, '.config', 'opencode-studio', 'studio.json');
@@ -1615,15 +1616,37 @@ app.delete('/api/auth/profiles/:provider/all', (req, res) => {
1615
1616
 
1616
1617
  const authCfg = loadAuthConfig() || {};
1617
1618
  if (authCfg[provider]) {
1618
- delete authCfg[provider];
1619
- if (provider === 'google') {
1620
- const key = 'google.antigravity';
1621
- delete authCfg.google;
1622
- delete authCfg[key];
1619
+ const paths = getPaths();
1620
+ const allPaths = paths.candidates;
1621
+ if (paths.current && !allPaths.includes(paths.current)) {
1622
+ allPaths.push(paths.current);
1623
1623
  }
1624
- const cp = getConfigPath();
1625
- const ap = path.join(path.dirname(cp), 'auth.json');
1626
- atomicWriteFileSync(ap, JSON.stringify(authCfg, null, 2));
1624
+
1625
+ allPaths.forEach(p => {
1626
+ const ap = path.join(path.dirname(p), 'auth.json');
1627
+ if (fs.existsSync(ap)) {
1628
+ try {
1629
+ const cfg = JSON.parse(fs.readFileSync(ap, 'utf8'));
1630
+ let modified = false;
1631
+
1632
+ if (provider === 'google') {
1633
+ if (cfg.google) { delete cfg.google; modified = true; }
1634
+ if (cfg['google.antigravity']) { delete cfg['google.antigravity']; modified = true; }
1635
+ if (cfg['google.gemini']) { delete cfg['google.gemini']; modified = true; }
1636
+ } else if (cfg[provider]) {
1637
+ delete cfg[provider];
1638
+ modified = true;
1639
+ }
1640
+
1641
+ if (modified) {
1642
+ console.log(`[Auth] Removing ${provider} (all) from ${ap}`);
1643
+ atomicWriteFileSync(ap, JSON.stringify(cfg, null, 2));
1644
+ }
1645
+ } catch (e) {
1646
+ console.error(`[Auth] Failed to update ${ap}:`, e.message);
1647
+ }
1648
+ }
1649
+ });
1627
1650
  }
1628
1651
 
1629
1652
  const metadata = loadPoolMetadata();
@@ -1692,9 +1715,51 @@ app.delete('/api/auth/profiles/:provider/:name', (req, res) => {
1692
1715
  }
1693
1716
 
1694
1717
  if (changed) {
1695
- const cp = getConfigPath();
1696
- const ap = path.join(path.dirname(cp), 'auth.json');
1697
- atomicWriteFileSync(ap, JSON.stringify(authCfg, null, 2));
1718
+ const paths = getPaths();
1719
+ const allPaths = paths.candidates;
1720
+ if (paths.current && !allPaths.includes(paths.current)) {
1721
+ allPaths.push(paths.current);
1722
+ }
1723
+
1724
+ allPaths.forEach(p => {
1725
+ const ap = path.join(path.dirname(p), 'auth.json');
1726
+ if (fs.existsSync(ap)) {
1727
+ try {
1728
+ const cfg = JSON.parse(fs.readFileSync(ap, 'utf8'));
1729
+ let modified = false;
1730
+
1731
+ if (provider === 'google') {
1732
+ if (cfg.google?.email === name || cfg['google.antigravity']?.email === name || cfg['google.gemini']?.email === name) {
1733
+ delete cfg.google;
1734
+ delete cfg['google.antigravity'];
1735
+ delete cfg['google.gemini'];
1736
+ modified = true;
1737
+ }
1738
+ } else if (cfg[provider]) {
1739
+ const creds = cfg[provider];
1740
+ let matches = false;
1741
+ if (creds.email === name) matches = true;
1742
+ else if (creds.accountId === name) matches = true;
1743
+ else if (provider === 'openai' && creds.access) {
1744
+ const decoded = decodeJWT(creds.access);
1745
+ if (decoded && decoded['https://api.openai.com/profile']?.email === name) matches = true;
1746
+ }
1747
+
1748
+ if (matches) {
1749
+ delete cfg[provider];
1750
+ modified = true;
1751
+ }
1752
+ }
1753
+
1754
+ if (modified) {
1755
+ console.log(`[Auth] Removing credentials from ${ap}`);
1756
+ atomicWriteFileSync(ap, JSON.stringify(cfg, null, 2));
1757
+ }
1758
+ } catch (e) {
1759
+ console.error(`[Auth] Failed to update ${ap}:`, e.message);
1760
+ }
1761
+ }
1762
+ });
1698
1763
  }
1699
1764
 
1700
1765
  const metadata = loadPoolMetadata();
@@ -2575,6 +2640,129 @@ app.post('/api/proxy/config', (req, res) => {
2575
2640
  res.json({ success: true });
2576
2641
  });
2577
2642
 
2643
+ const PROXY_CONFIG_PATH = path.join(HOME_DIR, '.config', 'opencode-studio', 'cliproxy.yaml');
2644
+
2645
+ app.get('/api/management/config.yaml', (req, res) => {
2646
+ console.log('GET config.yaml hit');
2647
+ if (!fs.existsSync(PROXY_CONFIG_PATH)) {
2648
+ console.log('Config file not found:', PROXY_CONFIG_PATH);
2649
+ return res.send("");
2650
+ }
2651
+ console.log('Sending config file:', PROXY_CONFIG_PATH);
2652
+ const content = fs.readFileSync(PROXY_CONFIG_PATH, 'utf8');
2653
+ res.send(content);
2654
+ });
2655
+
2656
+ app.put('/api/management/config.yaml', (req, res) => {
2657
+ fs.writeFileSync(PROXY_CONFIG_PATH, req.body);
2658
+ res.json({ ok: true, changed: [] });
2659
+ });
2660
+
2661
+ app.get('/api/management/api-keys', (req, res) => {
2662
+ if (!fs.existsSync(PROXY_CONFIG_PATH)) return res.json({ "api-keys": [] });
2663
+ const content = fs.readFileSync(PROXY_CONFIG_PATH, 'utf8');
2664
+ const yaml = require('js-yaml');
2665
+ try {
2666
+ const doc = yaml.load(content) || {};
2667
+ const keys = [];
2668
+ if (doc['management-key']) keys.push(doc['management-key']);
2669
+ if (Array.isArray(doc['api-keys'])) keys.push(...doc['api-keys']);
2670
+ res.json({ "api-keys": [...new Set(keys)].filter(k => k) });
2671
+ } catch (e) {
2672
+ res.json({ "api-keys": [] });
2673
+ }
2674
+ });
2675
+
2676
+ app.put('/api/management/api-keys', (req, res) => {
2677
+ const keys = req.body;
2678
+ if (!fs.existsSync(PROXY_CONFIG_PATH)) return res.status(404).json({ error: "Config not found" });
2679
+ const content = fs.readFileSync(PROXY_CONFIG_PATH, 'utf8');
2680
+ const yaml = require('js-yaml');
2681
+ try {
2682
+ const doc = yaml.load(content) || {};
2683
+ doc['api-keys'] = keys;
2684
+ if (keys.length > 0) doc['management-key'] = keys[0];
2685
+
2686
+ fs.writeFileSync(PROXY_CONFIG_PATH, yaml.dump(doc));
2687
+ res.json({ status: "ok" });
2688
+ } catch (e) {
2689
+ res.status(500).json({ error: e.message });
2690
+ }
2691
+ });
2692
+
2693
+ app.get('/api/management/logs', (req, res) => {
2694
+ if (!fs.existsSync(LOG_DIR)) return res.json({ lines: [], "line-count": 0, "latest-timestamp": Date.now() });
2695
+ const logFiles = fs.readdirSync(LOG_DIR).filter(f => f.endsWith('.log'));
2696
+ if (logFiles.length === 0) return res.json({ lines: [], "line-count": 0, "latest-timestamp": Date.now() });
2697
+
2698
+ const latest = logFiles.map(f => ({ name: f, time: fs.statSync(path.join(LOG_DIR, f)).mtime.getTime() }))
2699
+ .sort((a, b) => b.time - a.time)[0];
2700
+
2701
+ const content = fs.readFileSync(path.join(LOG_DIR, latest.name), 'utf8');
2702
+ const lines = content.split('\n').slice(-1000);
2703
+ res.json({ lines, "line-count": lines.length, "latest-timestamp": latest.time });
2704
+ });
2705
+
2706
+ app.get('/api/management/usage', (req, res) => {
2707
+ res.json({
2708
+ usage: {
2709
+ total_requests: 0,
2710
+ success_count: 0,
2711
+ failure_count: 0,
2712
+ total_tokens: 0,
2713
+ requests_by_day: {},
2714
+ requests_by_hour: {},
2715
+ tokens_by_day: {},
2716
+ tokens_by_hour: {},
2717
+ apis: {},
2718
+ failed_requests: 0
2719
+ }
2720
+ });
2721
+ });
2722
+
2723
+ app.get('/api/management/logging-to-file', (req, res) => {
2724
+ if (!fs.existsSync(PROXY_CONFIG_PATH)) return res.json({ "logging-to-file": false });
2725
+ try {
2726
+ const content = fs.readFileSync(PROXY_CONFIG_PATH, 'utf8');
2727
+ const yaml = require('js-yaml');
2728
+ const doc = yaml.load(content) || {};
2729
+ res.json({ "logging-to-file": !!doc['logging-to-file'] });
2730
+ } catch (e) {
2731
+ res.json({ "logging-to-file": false });
2732
+ }
2733
+ });
2734
+
2735
+ app.patch('/api/management/logging-to-file', (req, res) => {
2736
+ if (!fs.existsSync(PROXY_CONFIG_PATH)) return res.status(404).json({ error: "Config not found" });
2737
+ const enabled = req.body.value;
2738
+ const content = fs.readFileSync(PROXY_CONFIG_PATH, 'utf8');
2739
+ const yaml = require('js-yaml');
2740
+ try {
2741
+ const doc = yaml.load(content) || {};
2742
+ doc['logging-to-file'] = enabled;
2743
+ fs.writeFileSync(PROXY_CONFIG_PATH, yaml.dump(doc));
2744
+ res.json({ status: "ok" });
2745
+ } catch (e) {
2746
+ res.status(500).json({ error: e.message });
2747
+ }
2748
+ });
2749
+
2750
+ app.delete('/api/management/logs', (req, res) => {
2751
+ if (!fs.existsSync(LOG_DIR)) return res.json({ success: true, removed: 0 });
2752
+ const logFiles = fs.readdirSync(LOG_DIR).filter(f => f.endsWith('.log'));
2753
+ let removed = 0;
2754
+ try {
2755
+ logFiles.forEach(f => {
2756
+ const filePath = path.join(LOG_DIR, f);
2757
+ fs.unlinkSync(filePath);
2758
+ removed++;
2759
+ });
2760
+ res.json({ success: true, removed });
2761
+ } catch (e) {
2762
+ res.status(500).json({ error: e.message });
2763
+ }
2764
+ });
2765
+
2578
2766
  app.get('/api/profiles', (req, res) => {
2579
2767
  res.json(profileManager.listProfiles());
2580
2768
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-studio-server",
3
- "version": "1.16.2",
3
+ "version": "1.16.4",
4
4
  "description": "Backend server for OpenCode Studio - manages opencode configurations",
5
5
  "main": "index.js",
6
6
  "bin": {
package/server.log ADDED
@@ -0,0 +1 @@
1
+ Acceso denegado.