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.
- package/index.js +198 -0
- 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}`));
|