opencode-studio-server 1.12.14 → 1.14.0
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/AGENTS.md +1 -0
- package/README.md +1 -0
- package/backend.log +3 -0
- package/index.js +140 -1
- package/package.json +3 -2
- package/profile-manager.js +89 -0
- package/proxy-manager.js +162 -0
package/AGENTS.md
CHANGED
|
@@ -7,6 +7,7 @@ Express API backend (port 3001). Single-file architecture.
|
|
|
7
7
|
| File | Purpose |
|
|
8
8
|
|------|---------|
|
|
9
9
|
| `index.js` | All routes, config IO, auth, skills, plugins, usage stats |
|
|
10
|
+
| `proxy-manager.js` | Manages CLIProxyAPI process and config |
|
|
10
11
|
| `cli.js` | npm bin entry, protocol URL parser, pending action queue |
|
|
11
12
|
| `register-protocol.js` | OS-specific `opencodestudio://` handler registration |
|
|
12
13
|
|
package/README.md
CHANGED
|
@@ -31,6 +31,7 @@ The server runs on port **3001** and provides an API for managing your local Ope
|
|
|
31
31
|
- **Protocol Handler**: `opencodestudio://` support for one-click actions
|
|
32
32
|
- **Config Management**: Reads/writes `~/.config/opencode/opencode.json`
|
|
33
33
|
- **MCP Management**: Add/remove/toggle MCP servers
|
|
34
|
+
- **Proxy Manager**: Manage CLIProxyAPI instance
|
|
34
35
|
- **Auth**: Manage authentication profiles
|
|
35
36
|
|
|
36
37
|
## License
|
package/backend.log
ADDED
package/index.js
CHANGED
|
@@ -8,6 +8,8 @@ const crypto = require('crypto');
|
|
|
8
8
|
const { spawn, exec } = require('child_process');
|
|
9
9
|
|
|
10
10
|
const pkg = require('./package.json');
|
|
11
|
+
const proxyManager = require('./proxy-manager');
|
|
12
|
+
const profileManager = require('./profile-manager');
|
|
11
13
|
const SERVER_VERSION = pkg.version;
|
|
12
14
|
|
|
13
15
|
// Atomic file write: write to temp file then rename to prevent corruption
|
|
@@ -2439,6 +2441,112 @@ app.post('/api/auth/pool/quota/limit', (req, res) => {
|
|
|
2439
2441
|
res.json({ success: true, dailyLimit: limit });
|
|
2440
2442
|
});
|
|
2441
2443
|
|
|
2444
|
+
app.get('/api/proxy/status', async (req, res) => {
|
|
2445
|
+
const status = await proxyManager.getStatus();
|
|
2446
|
+
res.json(status);
|
|
2447
|
+
});
|
|
2448
|
+
|
|
2449
|
+
app.post('/api/proxy/start', async (req, res) => {
|
|
2450
|
+
const result = await proxyManager.startProxy();
|
|
2451
|
+
res.json(result);
|
|
2452
|
+
});
|
|
2453
|
+
|
|
2454
|
+
app.post('/api/proxy/stop', async (req, res) => {
|
|
2455
|
+
const result = proxyManager.stopProxy();
|
|
2456
|
+
res.json(result);
|
|
2457
|
+
});
|
|
2458
|
+
|
|
2459
|
+
app.post('/api/proxy/login', async (req, res) => {
|
|
2460
|
+
const { provider } = req.body;
|
|
2461
|
+
const result = await proxyManager.runLogin(provider);
|
|
2462
|
+
if (!result.success) return res.status(400).json(result);
|
|
2463
|
+
|
|
2464
|
+
const cmd = result.command;
|
|
2465
|
+
const cp = getConfigPath();
|
|
2466
|
+
const configDir = cp ? path.dirname(cp) : process.cwd();
|
|
2467
|
+
const safeDir = configDir.replace(/"/g, '\\"');
|
|
2468
|
+
const platform = process.platform;
|
|
2469
|
+
|
|
2470
|
+
if (platform === 'win32') {
|
|
2471
|
+
const terminalCmd = `start "" /d "${safeDir}" cmd /c "call ${cmd} || pause"`;
|
|
2472
|
+
exec(terminalCmd, (err) => {
|
|
2473
|
+
if (err) return res.status(500).json({ error: 'Failed to open terminal', details: err.message });
|
|
2474
|
+
res.json({ success: true, message: 'Terminal opened' });
|
|
2475
|
+
});
|
|
2476
|
+
} else if (platform === 'darwin') {
|
|
2477
|
+
const terminalCmd = `osascript -e 'tell application "Terminal" to do script "cd ${safeDir} && ${cmd}"'`;
|
|
2478
|
+
exec(terminalCmd, (err) => {
|
|
2479
|
+
if (err) return res.status(500).json({ error: 'Failed to open terminal', details: err.message });
|
|
2480
|
+
res.json({ success: true, message: 'Terminal opened' });
|
|
2481
|
+
});
|
|
2482
|
+
} else {
|
|
2483
|
+
const linuxTerminals = [
|
|
2484
|
+
{ name: 'x-terminal-emulator', cmd: `x-terminal-emulator -e "bash -c 'cd ${safeDir} && ${cmd}'"` },
|
|
2485
|
+
{ name: 'gnome-terminal', cmd: `gnome-terminal -- bash -c "cd ${safeDir} && ${cmd}; read -p 'Press Enter to close...'"` },
|
|
2486
|
+
{ name: 'konsole', cmd: `konsole -e bash -c "cd ${safeDir} && ${cmd}; read -p 'Press Enter to close...'"` },
|
|
2487
|
+
{ name: 'xfce4-terminal', cmd: `xfce4-terminal -e "bash -c \"cd ${safeDir} && ${cmd}; read -p 'Press Enter to close...'\"" ` },
|
|
2488
|
+
{ name: 'xterm', cmd: `xterm -e "bash -c 'cd ${safeDir} && ${cmd}; read -p Press_Enter_to_close...'"` }
|
|
2489
|
+
];
|
|
2490
|
+
|
|
2491
|
+
const tryTerminal = (index) => {
|
|
2492
|
+
if (index >= linuxTerminals.length) {
|
|
2493
|
+
return res.json({
|
|
2494
|
+
success: false,
|
|
2495
|
+
message: 'No terminal emulator found',
|
|
2496
|
+
note: 'Run this command manually:',
|
|
2497
|
+
command: cmd
|
|
2498
|
+
});
|
|
2499
|
+
}
|
|
2500
|
+
const terminal = linuxTerminals[index];
|
|
2501
|
+
exec(terminal.cmd, (err) => {
|
|
2502
|
+
if (err) {
|
|
2503
|
+
tryTerminal(index + 1);
|
|
2504
|
+
} else {
|
|
2505
|
+
res.json({ success: true, message: 'Terminal opened' });
|
|
2506
|
+
}
|
|
2507
|
+
});
|
|
2508
|
+
};
|
|
2509
|
+
tryTerminal(0);
|
|
2510
|
+
}
|
|
2511
|
+
});
|
|
2512
|
+
|
|
2513
|
+
app.get('/api/proxy/config', (req, res) => {
|
|
2514
|
+
res.json(proxyManager.loadProxyConfig());
|
|
2515
|
+
});
|
|
2516
|
+
|
|
2517
|
+
app.post('/api/proxy/config', (req, res) => {
|
|
2518
|
+
proxyManager.saveProxyConfig(req.body);
|
|
2519
|
+
res.json({ success: true });
|
|
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
|
+
|
|
2442
2550
|
// ============================================
|
|
2443
2551
|
// END ACCOUNT POOL MANAGEMENT
|
|
2444
2552
|
// ============================================
|
|
@@ -3055,4 +3163,35 @@ module.exports = {
|
|
|
3055
3163
|
loadStudioConfig,
|
|
3056
3164
|
saveStudioConfig,
|
|
3057
3165
|
buildAccountPool
|
|
3058
|
-
};
|
|
3166
|
+
};
|
|
3167
|
+
app.get('/api/prompts/global', (req, res) => {
|
|
3168
|
+
const cp = getConfigPath();
|
|
3169
|
+
const dir = cp ? path.dirname(cp) : path.join(os.homedir(), '.config', 'opencode');
|
|
3170
|
+
const globalPath = path.join(dir, 'OPENCODE.md');
|
|
3171
|
+
|
|
3172
|
+
if (fs.existsSync(globalPath)) {
|
|
3173
|
+
res.json({ content: fs.readFileSync(globalPath, 'utf8') });
|
|
3174
|
+
} else {
|
|
3175
|
+
res.json({ content: '' });
|
|
3176
|
+
}
|
|
3177
|
+
});
|
|
3178
|
+
|
|
3179
|
+
app.post('/api/prompts/global', (req, res) => {
|
|
3180
|
+
const { content } = req.body;
|
|
3181
|
+
const cp = getConfigPath();
|
|
3182
|
+
const dir = cp ? path.dirname(cp) : path.join(os.homedir(), '.config', 'opencode');
|
|
3183
|
+
const globalPath = path.join(dir, 'OPENCODE.md');
|
|
3184
|
+
|
|
3185
|
+
console.log(`[Prompts] Saving to: ${globalPath}`);
|
|
3186
|
+
|
|
3187
|
+
try {
|
|
3188
|
+
if (!fs.existsSync(dir)) {
|
|
3189
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
3190
|
+
}
|
|
3191
|
+
atomicWriteFileSync(globalPath, content);
|
|
3192
|
+
res.json({ success: true });
|
|
3193
|
+
} catch (err) {
|
|
3194
|
+
console.error('[Prompts] Error:', err);
|
|
3195
|
+
res.status(500).json({ error: err.message });
|
|
3196
|
+
}
|
|
3197
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-studio-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"description": "Backend server for OpenCode Studio - manages opencode configurations",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"body-parser": "^2.2.1",
|
|
29
29
|
"cors": "^2.8.5",
|
|
30
|
-
"express": "^5.2.1"
|
|
30
|
+
"express": "^5.2.1",
|
|
31
|
+
"js-yaml": "^4.1.1"
|
|
31
32
|
}
|
|
32
33
|
}
|
|
@@ -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
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
const { spawn, exec } = require('child_process');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const yaml = require('js-yaml');
|
|
6
|
+
|
|
7
|
+
const HOME_DIR = os.homedir();
|
|
8
|
+
const CONFIG_DIR = path.join(HOME_DIR, '.config', 'opencode-studio');
|
|
9
|
+
const PROXY_CONFIG_FILE = path.join(CONFIG_DIR, 'cliproxy.yaml');
|
|
10
|
+
const PROXY_AUTH_DIR = path.join(HOME_DIR, '.cli-proxy-api');
|
|
11
|
+
|
|
12
|
+
let proxyProcess = null;
|
|
13
|
+
let isProxyRunning = false;
|
|
14
|
+
|
|
15
|
+
// Helper to check if binary exists
|
|
16
|
+
const checkBinary = (cmd) => {
|
|
17
|
+
return new Promise((resolve) => {
|
|
18
|
+
const checkCmd = process.platform === 'win32' ? `where ${cmd}` : `which ${cmd}`;
|
|
19
|
+
exec(checkCmd, (err) => {
|
|
20
|
+
resolve(!err);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const getProxyCommand = async () => {
|
|
26
|
+
if (await checkBinary('cliproxyapi')) return 'cliproxyapi';
|
|
27
|
+
if (await checkBinary('cliproxy')) return 'cliproxy';
|
|
28
|
+
return null;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Config Management
|
|
32
|
+
const loadProxyConfig = () => {
|
|
33
|
+
if (!fs.existsSync(PROXY_CONFIG_FILE)) {
|
|
34
|
+
// Default config
|
|
35
|
+
const defaultConfig = {
|
|
36
|
+
port: 8317,
|
|
37
|
+
"auth-dir": PROXY_AUTH_DIR,
|
|
38
|
+
routing: { strategy: "round-robin" },
|
|
39
|
+
"quota-exceeded": {
|
|
40
|
+
"switch-project": true,
|
|
41
|
+
"switch-preview-model": true
|
|
42
|
+
},
|
|
43
|
+
"gemini-api-key": []
|
|
44
|
+
};
|
|
45
|
+
saveProxyConfig(defaultConfig);
|
|
46
|
+
return defaultConfig;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const content = fs.readFileSync(PROXY_CONFIG_FILE, 'utf8');
|
|
51
|
+
return yaml.load(content);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
console.error("Failed to load proxy config:", e);
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const saveProxyConfig = (config) => {
|
|
59
|
+
try {
|
|
60
|
+
if (!fs.existsSync(CONFIG_DIR)) fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
61
|
+
const content = yaml.dump(config);
|
|
62
|
+
fs.writeFileSync(PROXY_CONFIG_FILE, content);
|
|
63
|
+
} catch (e) {
|
|
64
|
+
console.error("Failed to save proxy config:", e);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Process Management
|
|
69
|
+
const startProxy = async () => {
|
|
70
|
+
if (isProxyRunning) return { success: true, message: "Already running" };
|
|
71
|
+
|
|
72
|
+
const cmd = await getProxyCommand();
|
|
73
|
+
if (!cmd) return { success: false, error: "CLIProxyAPI binary not found. Please install it." };
|
|
74
|
+
|
|
75
|
+
// Ensure config exists
|
|
76
|
+
if (!fs.existsSync(PROXY_CONFIG_FILE)) loadProxyConfig();
|
|
77
|
+
|
|
78
|
+
console.log(`Starting proxy with command: ${cmd} -config ${PROXY_CONFIG_FILE}`);
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
// -no-browser flag to prevent it from trying to open browser on startup if that's a thing
|
|
82
|
+
proxyProcess = spawn(cmd, ['-config', PROXY_CONFIG_FILE], {
|
|
83
|
+
detached: false,
|
|
84
|
+
stdio: 'pipe'
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
proxyProcess.stdout.on('data', (data) => {
|
|
88
|
+
console.log(`[Proxy] ${data}`);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
proxyProcess.stderr.on('data', (data) => {
|
|
92
|
+
console.error(`[Proxy Err] ${data}`);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
proxyProcess.on('close', (code) => {
|
|
96
|
+
console.log(`[Proxy] Exited with code ${code}`);
|
|
97
|
+
isProxyRunning = false;
|
|
98
|
+
proxyProcess = null;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
isProxyRunning = true;
|
|
102
|
+
return { success: true, pid: proxyProcess.pid };
|
|
103
|
+
} catch (e) {
|
|
104
|
+
return { success: false, error: e.message };
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const stopProxy = () => {
|
|
109
|
+
if (proxyProcess) {
|
|
110
|
+
proxyProcess.kill();
|
|
111
|
+
proxyProcess = null;
|
|
112
|
+
isProxyRunning = false;
|
|
113
|
+
return { success: true };
|
|
114
|
+
}
|
|
115
|
+
return { success: false, error: "Not running" };
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const getStatus = async () => {
|
|
119
|
+
const cmd = await getProxyCommand();
|
|
120
|
+
return {
|
|
121
|
+
running: isProxyRunning,
|
|
122
|
+
pid: proxyProcess?.pid,
|
|
123
|
+
configFile: PROXY_CONFIG_FILE,
|
|
124
|
+
port: 8317,
|
|
125
|
+
installed: !!cmd,
|
|
126
|
+
binary: cmd
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const runLogin = async (provider) => {
|
|
131
|
+
const cmd = await getProxyCommand();
|
|
132
|
+
if (!cmd) return { success: false, error: "Binary not found" };
|
|
133
|
+
|
|
134
|
+
let loginFlag = '';
|
|
135
|
+
switch(provider) {
|
|
136
|
+
case 'google':
|
|
137
|
+
case 'antigravity': loginFlag = '-antigravity-login'; break;
|
|
138
|
+
case 'openai':
|
|
139
|
+
case 'codex': loginFlag = '-codex-login'; break;
|
|
140
|
+
case 'anthropic': loginFlag = '-claude-login'; break;
|
|
141
|
+
default: return { success: false, error: "Unknown provider" };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Return the command so the UI can spawn a terminal for it
|
|
145
|
+
// We pass the config file so it saves the token to the right place/knows the auth-dir
|
|
146
|
+
const fullCmd = `${cmd} ${loginFlag} -config "${PROXY_CONFIG_FILE}"`;
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
success: true,
|
|
150
|
+
command: fullCmd,
|
|
151
|
+
message: "Terminal launching..."
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
module.exports = {
|
|
156
|
+
startProxy,
|
|
157
|
+
stopProxy,
|
|
158
|
+
getStatus,
|
|
159
|
+
loadProxyConfig,
|
|
160
|
+
saveProxyConfig,
|
|
161
|
+
runLogin
|
|
162
|
+
};
|