opencode-studio-server 1.2.2 → 1.3.1

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 +198 -0
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -79,6 +79,7 @@ function loadStudioConfig() {
79
79
  activeProfiles: {},
80
80
  activeGooglePlugin: 'gemini',
81
81
  availableGooglePlugins: [],
82
+ presets: [],
82
83
  pluginModels: {
83
84
  gemini: {
84
85
  "gemini-3-pro-preview": {
@@ -360,6 +361,21 @@ app.delete('/api/skills/:name', (req, res) => {
360
361
  res.json({ success: true });
361
362
  });
362
363
 
364
+ app.post('/api/skills/:name/toggle', (req, res) => {
365
+ const { name } = req.params;
366
+ const studio = loadStudioConfig();
367
+ studio.disabledSkills = studio.disabledSkills || [];
368
+
369
+ if (studio.disabledSkills.includes(name)) {
370
+ studio.disabledSkills = studio.disabledSkills.filter(s => s !== name);
371
+ } else {
372
+ studio.disabledSkills.push(name);
373
+ }
374
+
375
+ saveStudioConfig(studio);
376
+ res.json({ success: true, enabled: !studio.disabledSkills.includes(name) });
377
+ });
378
+
363
379
  const getPluginDir = () => {
364
380
  const cp = getConfigPath();
365
381
  return cp ? path.join(path.dirname(cp), 'plugin') : null;
@@ -403,6 +419,79 @@ app.get('/api/plugins', (req, res) => {
403
419
  res.json(plugins);
404
420
  });
405
421
 
422
+ app.get('/api/plugins/:name', (req, res) => {
423
+ const { name } = req.params;
424
+ const pd = getPluginDir();
425
+
426
+ const possiblePaths = [
427
+ path.join(pd, name + '.js'),
428
+ path.join(pd, name + '.ts'),
429
+ path.join(pd, name, 'index.js'),
430
+ path.join(pd, name, 'index.ts')
431
+ ];
432
+
433
+ for (const p of possiblePaths) {
434
+ if (fs.existsSync(p)) {
435
+ const content = fs.readFileSync(p, 'utf8');
436
+ return res.json({ name, content });
437
+ }
438
+ }
439
+ res.status(404).json({ error: 'Plugin not found' });
440
+ });
441
+
442
+ app.post('/api/plugins/:name', (req, res) => {
443
+ const { name } = req.params;
444
+ const { content } = req.body;
445
+ const pd = getPluginDir();
446
+ if (!fs.existsSync(pd)) fs.mkdirSync(pd, { recursive: true });
447
+
448
+ // Default to .js if new
449
+ const filePath = path.join(pd, name.endsWith('.js') || name.endsWith('.ts') ? name : name + '.js');
450
+ atomicWriteFileSync(filePath, content);
451
+ res.json({ success: true });
452
+ });
453
+
454
+ app.delete('/api/plugins/:name', (req, res) => {
455
+ const { name } = req.params;
456
+ const pd = getPluginDir();
457
+
458
+ const possiblePaths = [
459
+ path.join(pd, name),
460
+ path.join(pd, name + '.js'),
461
+ path.join(pd, name + '.ts')
462
+ ];
463
+
464
+ let deleted = false;
465
+ for (const p of possiblePaths) {
466
+ if (fs.existsSync(p)) {
467
+ if (fs.statSync(p).isDirectory()) {
468
+ fs.rmSync(p, { recursive: true, force: true });
469
+ } else {
470
+ fs.unlinkSync(p);
471
+ }
472
+ deleted = true;
473
+ }
474
+ }
475
+
476
+ if (deleted) res.json({ success: true });
477
+ else res.status(404).json({ error: 'Plugin not found' });
478
+ });
479
+
480
+ app.post('/api/plugins/:name/toggle', (req, res) => {
481
+ const { name } = req.params;
482
+ const studio = loadStudioConfig();
483
+ studio.disabledPlugins = studio.disabledPlugins || [];
484
+
485
+ if (studio.disabledPlugins.includes(name)) {
486
+ studio.disabledPlugins = studio.disabledPlugins.filter(p => p !== name);
487
+ } else {
488
+ studio.disabledPlugins.push(name);
489
+ }
490
+
491
+ saveStudioConfig(studio);
492
+ res.json({ success: true, enabled: !studio.disabledPlugins.includes(name) });
493
+ });
494
+
406
495
  const getActiveGooglePlugin = () => {
407
496
  const studio = loadStudioConfig();
408
497
  return studio.activeGooglePlugin || null;
@@ -1520,4 +1609,113 @@ app.post('/api/plugins/config/add', (req, res) => {
1520
1609
  res.json({ added, skipped });
1521
1610
  });
1522
1611
 
1612
+ // Presets
1613
+ app.get('/api/presets', (req, res) => {
1614
+ const studio = loadStudioConfig();
1615
+ res.json(studio.presets || []);
1616
+ });
1617
+
1618
+ app.post('/api/presets', (req, res) => {
1619
+ const { name, description, config } = req.body;
1620
+ const studio = loadStudioConfig();
1621
+ const id = crypto.randomUUID();
1622
+ const preset = { id, name, description, config };
1623
+ studio.presets = studio.presets || [];
1624
+ studio.presets.push(preset);
1625
+ saveStudioConfig(studio);
1626
+ res.json(preset);
1627
+ });
1628
+
1629
+ app.put('/api/presets/:id', (req, res) => {
1630
+ const { id } = req.params;
1631
+ const { name, description, config } = req.body;
1632
+ const studio = loadStudioConfig();
1633
+ const index = (studio.presets || []).findIndex(p => p.id === id);
1634
+ if (index === -1) return res.status(404).json({ error: 'Preset not found' });
1635
+
1636
+ studio.presets[index] = { ...studio.presets[index], name, description, config };
1637
+ saveStudioConfig(studio);
1638
+ res.json(studio.presets[index]);
1639
+ });
1640
+
1641
+ app.delete('/api/presets/:id', (req, res) => {
1642
+ const { id } = req.params;
1643
+ const studio = loadStudioConfig();
1644
+ studio.presets = (studio.presets || []).filter(p => p.id !== id);
1645
+ saveStudioConfig(studio);
1646
+ res.json({ success: true });
1647
+ });
1648
+
1649
+ app.post('/api/presets/:id/apply', (req, res) => {
1650
+ const { id } = req.params;
1651
+ const { mode } = req.body; // 'exclusive', 'additive'
1652
+
1653
+ const studio = loadStudioConfig();
1654
+ const preset = (studio.presets || []).find(p => p.id === id);
1655
+ if (!preset) return res.status(404).json({ error: 'Preset not found' });
1656
+
1657
+ const config = loadConfig() || {};
1658
+ const cp = getConfigPath();
1659
+ const configDir = path.dirname(cp);
1660
+ const skillDir = path.join(configDir, 'skill');
1661
+ const pluginDir = path.join(configDir, 'plugin');
1662
+
1663
+ // Skills
1664
+ if (preset.config.skills !== undefined && preset.config.skills !== null) {
1665
+ const targetSkills = new Set(preset.config.skills);
1666
+ if (mode === 'exclusive') {
1667
+ const allSkills = [];
1668
+ if (fs.existsSync(skillDir)) {
1669
+ const dirents = fs.readdirSync(skillDir, { withFileTypes: true });
1670
+ for (const dirent of dirents) {
1671
+ if (dirent.isDirectory()) {
1672
+ if (fs.existsSync(path.join(skillDir, dirent.name, 'SKILL.md'))) {
1673
+ allSkills.push(dirent.name);
1674
+ }
1675
+ } else if (dirent.name.endsWith('.md')) {
1676
+ allSkills.push(dirent.name.replace('.md', ''));
1677
+ }
1678
+ }
1679
+ }
1680
+ studio.disabledSkills = allSkills.filter(s => !targetSkills.has(s));
1681
+ } else { // additive
1682
+ studio.disabledSkills = (studio.disabledSkills || []).filter(s => !targetSkills.has(s));
1683
+ }
1684
+ }
1685
+
1686
+ // Plugins
1687
+ if (preset.config.plugins !== undefined && preset.config.plugins !== null) {
1688
+ const targetPlugins = new Set(preset.config.plugins);
1689
+ if (mode === 'exclusive') {
1690
+ const allPlugins = [...(config.plugin || [])];
1691
+ if (fs.existsSync(pluginDir)) {
1692
+ const files = fs.readdirSync(pluginDir).filter(f => f.endsWith('.js') || f.endsWith('.ts'));
1693
+ allPlugins.push(...files.map(f => f.replace(/\.[^/.]+$/, "")));
1694
+ }
1695
+ const uniquePlugins = [...new Set(allPlugins)];
1696
+ studio.disabledPlugins = uniquePlugins.filter(p => !targetPlugins.has(p));
1697
+ } else { // additive
1698
+ studio.disabledPlugins = (studio.disabledPlugins || []).filter(p => !targetPlugins.has(p));
1699
+ }
1700
+ }
1701
+
1702
+ // MCPs
1703
+ if (preset.config.mcps !== undefined && preset.config.mcps !== null) {
1704
+ const targetMcps = new Set(preset.config.mcps);
1705
+ if (config.mcp) {
1706
+ for (const key in config.mcp) {
1707
+ if (mode === 'exclusive') {
1708
+ config.mcp[key].enabled = targetMcps.has(key);
1709
+ } else { // additive
1710
+ if (targetMcps.has(key)) config.mcp[key].enabled = true;
1711
+ }
1712
+ }
1713
+ }
1714
+ }
1715
+
1716
+ saveStudioConfig(studio);
1717
+ saveConfig(config);
1718
+ res.json({ success: true });
1719
+ });
1720
+
1523
1721
  app.listen(PORT, () => console.log(`Server running at http://localhost:${PORT}`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-studio-server",
3
- "version": "1.2.2",
3
+ "version": "1.3.1",
4
4
  "description": "Backend server for OpenCode Studio - manages opencode configurations",
5
5
  "main": "index.js",
6
6
  "bin": {