opencode-studio-server 1.13.0 → 1.14.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/backend.log ADDED
@@ -0,0 +1,4 @@
1
+ [Auth] Syncing github-copilot login for profile-1768679085919 to pool.
2
+ Server running at http://localhost:3001
3
+ [Profiles] Migrating existing config to "default" profile
4
+ [Prompts] Saving to: C:\Users\Microck\.config\opencode\OPENCODE.md
package/index.js CHANGED
@@ -9,6 +9,7 @@ const { spawn, exec } = require('child_process');
9
9
 
10
10
  const pkg = require('./package.json');
11
11
  const proxyManager = require('./proxy-manager');
12
+ const profileManager = require('./profile-manager');
12
13
  const SERVER_VERSION = pkg.version;
13
14
 
14
15
  // Atomic file write: write to temp file then rename to prevent corruption
@@ -2518,6 +2519,34 @@ app.post('/api/proxy/config', (req, res) => {
2518
2519
  res.json({ success: true });
2519
2520
  });
2520
2521
 
2522
+ app.get('/api/profiles', (req, res) => {
2523
+ res.json(profileManager.listProfiles());
2524
+ });
2525
+
2526
+ app.post('/api/profiles', (req, res) => {
2527
+ try {
2528
+ res.json(profileManager.createProfile(req.body.name));
2529
+ } catch (e) {
2530
+ res.status(400).json({ error: e.message });
2531
+ }
2532
+ });
2533
+
2534
+ app.delete('/api/profiles/:name', (req, res) => {
2535
+ try {
2536
+ res.json(profileManager.deleteProfile(req.params.name));
2537
+ } catch (e) {
2538
+ res.status(400).json({ error: e.message });
2539
+ }
2540
+ });
2541
+
2542
+ app.post('/api/profiles/:name/activate', (req, res) => {
2543
+ try {
2544
+ res.json(profileManager.activateProfile(req.params.name));
2545
+ } catch (e) {
2546
+ res.status(400).json({ error: e.message });
2547
+ }
2548
+ });
2549
+
2521
2550
  // ============================================
2522
2551
  // END ACCOUNT POOL MANAGEMENT
2523
2552
  // ============================================
@@ -3137,7 +3166,8 @@ module.exports = {
3137
3166
  };
3138
3167
  app.get('/api/prompts/global', (req, res) => {
3139
3168
  const cp = getConfigPath();
3140
- const globalPath = path.join(path.dirname(cp), 'OPENCODE.md');
3169
+ const dir = cp ? path.dirname(cp) : path.join(os.homedir(), '.config', 'opencode');
3170
+ const globalPath = path.join(dir, 'OPENCODE.md');
3141
3171
 
3142
3172
  if (fs.existsSync(globalPath)) {
3143
3173
  res.json({ content: fs.readFileSync(globalPath, 'utf8') });
@@ -3149,12 +3179,19 @@ app.get('/api/prompts/global', (req, res) => {
3149
3179
  app.post('/api/prompts/global', (req, res) => {
3150
3180
  const { content } = req.body;
3151
3181
  const cp = getConfigPath();
3152
- const globalPath = path.join(path.dirname(cp), 'OPENCODE.md');
3182
+ const dir = cp ? path.dirname(cp) : path.join(os.homedir(), '.config', 'opencode');
3183
+ const globalPath = path.join(dir, 'OPENCODE.md');
3153
3184
 
3185
+ console.log(`[Prompts] Saving to: ${globalPath}`);
3186
+
3154
3187
  try {
3188
+ if (!fs.existsSync(dir)) {
3189
+ fs.mkdirSync(dir, { recursive: true });
3190
+ }
3155
3191
  atomicWriteFileSync(globalPath, content);
3156
3192
  res.json({ success: true });
3157
3193
  } catch (err) {
3194
+ console.error('[Prompts] Error:', err);
3158
3195
  res.status(500).json({ error: err.message });
3159
3196
  }
3160
3197
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-studio-server",
3
- "version": "1.13.0",
3
+ "version": "1.14.1",
4
4
  "description": "Backend server for OpenCode Studio - manages opencode configurations",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -0,0 +1,89 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ const HOME_DIR = os.homedir();
6
+ const OPENCODE_DIR = path.join(HOME_DIR, '.config', 'opencode');
7
+ const PROFILES_DIR = path.join(HOME_DIR, '.config', 'opencode-profiles');
8
+
9
+ if (!fs.existsSync(PROFILES_DIR)) {
10
+ fs.mkdirSync(PROFILES_DIR, { recursive: true });
11
+ }
12
+
13
+ function isSymlink(filepath) {
14
+ try {
15
+ return fs.lstatSync(filepath).isSymbolicLink();
16
+ } catch {
17
+ return false;
18
+ }
19
+ }
20
+
21
+ function init() {
22
+ const defaultProfilePath = path.join(PROFILES_DIR, 'default');
23
+
24
+ if (fs.existsSync(OPENCODE_DIR) && !isSymlink(OPENCODE_DIR)) {
25
+ if (!fs.existsSync(defaultProfilePath)) {
26
+ console.log('[Profiles] Migrating existing config to "default" profile');
27
+ fs.renameSync(OPENCODE_DIR, defaultProfilePath);
28
+ fs.symlinkSync(defaultProfilePath, OPENCODE_DIR, 'junction');
29
+ }
30
+ } else if (!fs.existsSync(OPENCODE_DIR) && !isSymlink(OPENCODE_DIR)) {
31
+ fs.mkdirSync(defaultProfilePath, { recursive: true });
32
+ fs.symlinkSync(defaultProfilePath, OPENCODE_DIR, 'junction');
33
+ }
34
+ }
35
+
36
+ function listProfiles() {
37
+ init();
38
+ const profiles = fs.readdirSync(PROFILES_DIR).filter(f => {
39
+ return fs.statSync(path.join(PROFILES_DIR, f)).isDirectory();
40
+ });
41
+
42
+ let active = null;
43
+ if (isSymlink(OPENCODE_DIR)) {
44
+ const target = fs.readlinkSync(OPENCODE_DIR);
45
+ active = path.basename(target);
46
+ } else {
47
+ active = 'default (unmanaged)';
48
+ }
49
+
50
+ return { profiles, active };
51
+ }
52
+
53
+ function createProfile(name) {
54
+ const dir = path.join(PROFILES_DIR, name);
55
+ if (fs.existsSync(dir)) throw new Error('Profile already exists');
56
+ fs.mkdirSync(dir, { recursive: true });
57
+ return { success: true };
58
+ }
59
+
60
+ function deleteProfile(name) {
61
+ const { active } = listProfiles();
62
+ if (name === active) throw new Error('Cannot delete active profile');
63
+ if (name === 'default') throw new Error('Cannot delete default profile');
64
+
65
+ const dir = path.join(PROFILES_DIR, name);
66
+ if (fs.existsSync(dir)) {
67
+ fs.rmSync(dir, { recursive: true, force: true });
68
+ }
69
+ return { success: true };
70
+ }
71
+
72
+ function activateProfile(name) {
73
+ const target = path.join(PROFILES_DIR, name);
74
+ if (!fs.existsSync(target)) throw new Error('Profile not found');
75
+
76
+ if (fs.existsSync(OPENCODE_DIR)) {
77
+ fs.rmSync(OPENCODE_DIR, { recursive: true, force: true });
78
+ }
79
+
80
+ fs.symlinkSync(target, OPENCODE_DIR, 'junction');
81
+ return { success: true };
82
+ }
83
+
84
+ module.exports = {
85
+ listProfiles,
86
+ createProfile,
87
+ deleteProfile,
88
+ activateProfile
89
+ };
package/proxy-manager.js CHANGED
@@ -15,6 +15,9 @@ let isProxyRunning = false;
15
15
  // Helper to check if binary exists
16
16
  const checkBinary = (cmd) => {
17
17
  return new Promise((resolve) => {
18
+ if (path.isAbsolute(cmd)) {
19
+ return resolve(fs.existsSync(cmd));
20
+ }
18
21
  const checkCmd = process.platform === 'win32' ? `where ${cmd}` : `which ${cmd}`;
19
22
  exec(checkCmd, (err) => {
20
23
  resolve(!err);
@@ -23,7 +26,20 @@ const checkBinary = (cmd) => {
23
26
  };
24
27
 
25
28
  const getProxyCommand = async () => {
29
+ if (process.platform === 'win32') {
30
+ const localAppData = process.env.LOCALAPPDATA;
31
+ if (localAppData) {
32
+ const wingetPath = path.join(localAppData, 'Microsoft', 'WinGet', 'Links', 'CLIProxyAPI.exe');
33
+ if (fs.existsSync(wingetPath)) return wingetPath;
34
+
35
+ const progPath = path.join(localAppData, 'Programs', 'CLIProxyAPI', 'CLIProxyAPI.exe');
36
+ if (fs.existsSync(progPath)) return progPath;
37
+ }
38
+ }
39
+
26
40
  if (await checkBinary('cliproxyapi')) return 'cliproxyapi';
41
+ if (await checkBinary('CLIProxyAPI')) return 'CLIProxyAPI';
42
+ if (await checkBinary('cliproxyapi.exe')) return 'cliproxyapi.exe';
27
43
  if (await checkBinary('cliproxy')) return 'cliproxy';
28
44
  return null;
29
45
  };