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 +4 -0
- package/index.js +39 -2
- package/package.json +1 -1
- package/profile-manager.js +89 -0
- package/proxy-manager.js +16 -0
package/backend.log
ADDED
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
|
|
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
|
|
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
|
@@ -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
|
};
|