@openagents-org/agent-connector 0.2.7 → 0.2.9
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/package.json +1 -1
- package/src/autostart.js +178 -0
- package/src/cli.js +13 -0
- package/src/installer.js +136 -12
- package/src/paths.js +18 -2
package/package.json
CHANGED
package/src/autostart.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Autostart — register agent-connector daemon as a system service.
|
|
3
|
+
*
|
|
4
|
+
* - macOS: launchd plist
|
|
5
|
+
* - Linux: systemd user unit
|
|
6
|
+
* - Windows: Task Scheduler XML
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { execSync } = require('child_process');
|
|
14
|
+
const { whichBinary, IS_WINDOWS } = require('./paths');
|
|
15
|
+
|
|
16
|
+
const IS_MACOS = process.platform === 'darwin';
|
|
17
|
+
const IS_LINUX = process.platform === 'linux' && !IS_MACOS;
|
|
18
|
+
const HOME = process.env.HOME || process.env.USERPROFILE || '';
|
|
19
|
+
|
|
20
|
+
const SERVICE_LABEL = 'org.openagents.connector';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Enable autostart on login.
|
|
24
|
+
*/
|
|
25
|
+
function enable(configDir) {
|
|
26
|
+
const nodeBin = whichBinary('node') || process.execPath;
|
|
27
|
+
const cliPath = path.resolve(__dirname, '..', 'bin', 'agent-connector.js');
|
|
28
|
+
|
|
29
|
+
if (IS_MACOS) return _enableMacOS(nodeBin, cliPath, configDir);
|
|
30
|
+
if (IS_LINUX) return _enableLinux(nodeBin, cliPath, configDir);
|
|
31
|
+
if (IS_WINDOWS) return _enableWindows(nodeBin, cliPath, configDir);
|
|
32
|
+
throw new Error(`Autostart not supported on ${process.platform}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Disable autostart.
|
|
37
|
+
*/
|
|
38
|
+
function disable() {
|
|
39
|
+
if (IS_MACOS) return _disableMacOS();
|
|
40
|
+
if (IS_LINUX) return _disableLinux();
|
|
41
|
+
if (IS_WINDOWS) return _disableWindows();
|
|
42
|
+
throw new Error(`Autostart not supported on ${process.platform}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Check if autostart is enabled.
|
|
47
|
+
*/
|
|
48
|
+
function isEnabled() {
|
|
49
|
+
if (IS_MACOS) {
|
|
50
|
+
const plistPath = path.join(HOME, 'Library', 'LaunchAgents', `${SERVICE_LABEL}.plist`);
|
|
51
|
+
return fs.existsSync(plistPath);
|
|
52
|
+
}
|
|
53
|
+
if (IS_LINUX) {
|
|
54
|
+
const unitPath = path.join(HOME, '.config', 'systemd', 'user', 'openagents-connector.service');
|
|
55
|
+
return fs.existsSync(unitPath);
|
|
56
|
+
}
|
|
57
|
+
if (IS_WINDOWS) {
|
|
58
|
+
try {
|
|
59
|
+
execSync(`schtasks /Query /TN "OpenAgents Connector"`, { stdio: 'pipe', timeout: 5000 });
|
|
60
|
+
return true;
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ---- macOS: launchd ----
|
|
69
|
+
|
|
70
|
+
function _enableMacOS(nodeBin, cliPath, configDir) {
|
|
71
|
+
const plistDir = path.join(HOME, 'Library', 'LaunchAgents');
|
|
72
|
+
fs.mkdirSync(plistDir, { recursive: true });
|
|
73
|
+
|
|
74
|
+
const plistPath = path.join(plistDir, `${SERVICE_LABEL}.plist`);
|
|
75
|
+
const logPath = path.join(configDir, 'daemon.log');
|
|
76
|
+
|
|
77
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
78
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
79
|
+
<plist version="1.0">
|
|
80
|
+
<dict>
|
|
81
|
+
<key>Label</key>
|
|
82
|
+
<string>${SERVICE_LABEL}</string>
|
|
83
|
+
<key>ProgramArguments</key>
|
|
84
|
+
<array>
|
|
85
|
+
<string>${nodeBin}</string>
|
|
86
|
+
<string>${cliPath}</string>
|
|
87
|
+
<string>up</string>
|
|
88
|
+
<string>--foreground</string>
|
|
89
|
+
</array>
|
|
90
|
+
<key>RunAtLoad</key>
|
|
91
|
+
<true/>
|
|
92
|
+
<key>KeepAlive</key>
|
|
93
|
+
<true/>
|
|
94
|
+
<key>StandardOutPath</key>
|
|
95
|
+
<string>${logPath}</string>
|
|
96
|
+
<key>StandardErrorPath</key>
|
|
97
|
+
<string>${logPath}</string>
|
|
98
|
+
<key>EnvironmentVariables</key>
|
|
99
|
+
<dict>
|
|
100
|
+
<key>PATH</key>
|
|
101
|
+
<string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
|
|
102
|
+
</dict>
|
|
103
|
+
</dict>
|
|
104
|
+
</plist>`;
|
|
105
|
+
|
|
106
|
+
fs.writeFileSync(plistPath, plist, 'utf-8');
|
|
107
|
+
execSync(`launchctl load -w "${plistPath}"`, { stdio: 'pipe', timeout: 5000 });
|
|
108
|
+
return { enabled: true, path: plistPath };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function _disableMacOS() {
|
|
112
|
+
const plistPath = path.join(HOME, 'Library', 'LaunchAgents', `${SERVICE_LABEL}.plist`);
|
|
113
|
+
try {
|
|
114
|
+
execSync(`launchctl unload "${plistPath}"`, { stdio: 'pipe', timeout: 5000 });
|
|
115
|
+
} catch {}
|
|
116
|
+
try { fs.unlinkSync(plistPath); } catch {}
|
|
117
|
+
return { enabled: false };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ---- Linux: systemd user unit ----
|
|
121
|
+
|
|
122
|
+
function _enableLinux(nodeBin, cliPath, configDir) {
|
|
123
|
+
const unitDir = path.join(HOME, '.config', 'systemd', 'user');
|
|
124
|
+
fs.mkdirSync(unitDir, { recursive: true });
|
|
125
|
+
|
|
126
|
+
const unitPath = path.join(unitDir, 'openagents-connector.service');
|
|
127
|
+
|
|
128
|
+
const unit = `[Unit]
|
|
129
|
+
Description=OpenAgents Connector Daemon
|
|
130
|
+
After=network.target
|
|
131
|
+
|
|
132
|
+
[Service]
|
|
133
|
+
Type=simple
|
|
134
|
+
ExecStart=${nodeBin} ${cliPath} up --foreground
|
|
135
|
+
Restart=on-failure
|
|
136
|
+
RestartSec=10
|
|
137
|
+
Environment=PATH=/usr/local/bin:/usr/bin:/bin
|
|
138
|
+
|
|
139
|
+
[Install]
|
|
140
|
+
WantedBy=default.target
|
|
141
|
+
`;
|
|
142
|
+
|
|
143
|
+
fs.writeFileSync(unitPath, unit, 'utf-8');
|
|
144
|
+
execSync('systemctl --user daemon-reload', { stdio: 'pipe', timeout: 5000 });
|
|
145
|
+
execSync('systemctl --user enable openagents-connector.service', { stdio: 'pipe', timeout: 5000 });
|
|
146
|
+
execSync('systemctl --user start openagents-connector.service', { stdio: 'pipe', timeout: 5000 });
|
|
147
|
+
return { enabled: true, path: unitPath };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function _disableLinux() {
|
|
151
|
+
try {
|
|
152
|
+
execSync('systemctl --user stop openagents-connector.service', { stdio: 'pipe', timeout: 5000 });
|
|
153
|
+
} catch {}
|
|
154
|
+
try {
|
|
155
|
+
execSync('systemctl --user disable openagents-connector.service', { stdio: 'pipe', timeout: 5000 });
|
|
156
|
+
} catch {}
|
|
157
|
+
const unitPath = path.join(HOME, '.config', 'systemd', 'user', 'openagents-connector.service');
|
|
158
|
+
try { fs.unlinkSync(unitPath); } catch {}
|
|
159
|
+
return { enabled: false };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ---- Windows: Task Scheduler ----
|
|
163
|
+
|
|
164
|
+
function _enableWindows(nodeBin, cliPath, configDir) {
|
|
165
|
+
const taskName = 'OpenAgents Connector';
|
|
166
|
+
const cmd = `schtasks /Create /SC ONLOGON /TN "${taskName}" /TR "\\"${nodeBin}\\" \\"${cliPath}\\" up --foreground" /RL HIGHEST /F`;
|
|
167
|
+
execSync(cmd, { stdio: 'pipe', timeout: 10000 });
|
|
168
|
+
return { enabled: true, method: 'Task Scheduler' };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function _disableWindows() {
|
|
172
|
+
try {
|
|
173
|
+
execSync('schtasks /Delete /TN "OpenAgents Connector" /F', { stdio: 'pipe', timeout: 5000 });
|
|
174
|
+
} catch {}
|
|
175
|
+
return { enabled: false };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
module.exports = { enable, disable, isEnabled };
|
package/src/cli.js
CHANGED
|
@@ -327,6 +327,17 @@ async function cmdLogs(connector, flags, positional) {
|
|
|
327
327
|
}
|
|
328
328
|
}
|
|
329
329
|
|
|
330
|
+
async function cmdAutostart(connector, flags) {
|
|
331
|
+
const autostart = require('./autostart');
|
|
332
|
+
if (flags.disable) {
|
|
333
|
+
autostart.disable();
|
|
334
|
+
print('Autostart disabled.');
|
|
335
|
+
} else {
|
|
336
|
+
const result = autostart.enable(connector._config ? connector._config.configDir : require('path').join(require('os').homedir(), '.openagents'));
|
|
337
|
+
print(`Autostart enabled.${result.path ? ` Config: ${result.path}` : ''}`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
330
341
|
async function cmdWorkspace(connector, flags, positional) {
|
|
331
342
|
const sub = positional[0] || 'list';
|
|
332
343
|
const subArgs = positional.slice(1);
|
|
@@ -461,6 +472,7 @@ Commands:
|
|
|
461
472
|
connect <agent> <token> Connect agent to workspace
|
|
462
473
|
disconnect <agent> Disconnect agent from workspace
|
|
463
474
|
env <type> [--set K=V] View/set env vars for agent type
|
|
475
|
+
autostart [--disable] Enable/disable auto-start on login
|
|
464
476
|
test-llm <type> Test LLM connection
|
|
465
477
|
logs [agent] [--lines N] View daemon logs
|
|
466
478
|
workspace create [name] Create a new workspace
|
|
@@ -502,6 +514,7 @@ async function main() {
|
|
|
502
514
|
connect: () => cmdConnect(connector, flags, positional),
|
|
503
515
|
disconnect: () => cmdDisconnect(connector, flags, positional),
|
|
504
516
|
logs: () => cmdLogs(connector, flags, positional),
|
|
517
|
+
autostart: () => cmdAutostart(connector, flags),
|
|
505
518
|
workspace: () => cmdWorkspace(connector, flags, positional),
|
|
506
519
|
env: () => cmdEnv(connector, flags, positional),
|
|
507
520
|
'test-llm': () => cmdTestLLM(connector, flags, positional),
|
package/src/installer.js
CHANGED
|
@@ -94,6 +94,60 @@ class Installer {
|
|
|
94
94
|
return { success: true, output };
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Install with streaming output via callback.
|
|
99
|
+
* @param {string} agentType
|
|
100
|
+
* @param {function(string)} onData - called with each chunk of output
|
|
101
|
+
* @returns {Promise<{success: boolean, command: string}>}
|
|
102
|
+
*/
|
|
103
|
+
async installStreaming(agentType, onData) {
|
|
104
|
+
const { spawn } = require('child_process');
|
|
105
|
+
const entry = this.registry.getEntry(agentType);
|
|
106
|
+
if (!entry || !entry.install) {
|
|
107
|
+
throw new Error(`No install definition for agent type: ${agentType}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let cmd = this._getInstallCommand(entry.install);
|
|
111
|
+
if (!cmd) {
|
|
112
|
+
throw new Error(`No install command for ${agentType} on ${Installer.platform()}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Add verbose logging for npm so user sees download progress on stderr
|
|
116
|
+
if (cmd.includes('npm install') && !cmd.includes('--loglevel')) {
|
|
117
|
+
cmd = cmd.replace('npm install', 'npm install --loglevel=verbose');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (onData) onData(`$ ${cmd}\n\n`);
|
|
121
|
+
|
|
122
|
+
const env = this._buildShellEnv();
|
|
123
|
+
const shell = process.platform === 'win32'
|
|
124
|
+
? (process.env.ComSpec || 'C:\\Windows\\System32\\cmd.exe')
|
|
125
|
+
: true;
|
|
126
|
+
|
|
127
|
+
return new Promise((resolve, reject) => {
|
|
128
|
+
const proc = spawn(cmd, [], { shell, env, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
129
|
+
|
|
130
|
+
if (proc.stdout) proc.stdout.setEncoding('utf-8');
|
|
131
|
+
if (proc.stderr) proc.stderr.setEncoding('utf-8');
|
|
132
|
+
|
|
133
|
+
if (proc.stdout) proc.stdout.on('data', (d) => { if (onData) onData(d); });
|
|
134
|
+
if (proc.stderr) proc.stderr.on('data', (d) => { if (onData) onData(d); });
|
|
135
|
+
|
|
136
|
+
proc.on('error', (err) => reject(err));
|
|
137
|
+
proc.on('close', (code) => {
|
|
138
|
+
if (code === 0) {
|
|
139
|
+
this._markInstalled(agentType);
|
|
140
|
+
if (onData) onData(`\nDone! ${agentType} is now installed.\n`);
|
|
141
|
+
resolve({ success: true, command: cmd });
|
|
142
|
+
} else {
|
|
143
|
+
const msg = `Install failed with exit code ${code}`;
|
|
144
|
+
if (onData) onData(`\n${msg}\n`);
|
|
145
|
+
reject(new Error(msg));
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
97
151
|
/**
|
|
98
152
|
* Uninstall an agent runtime.
|
|
99
153
|
* @returns {Promise<{success: boolean, output: string}>}
|
|
@@ -115,6 +169,58 @@ class Installer {
|
|
|
115
169
|
return { success: true, output };
|
|
116
170
|
}
|
|
117
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Uninstall with streaming output via callback.
|
|
174
|
+
*/
|
|
175
|
+
async uninstallStreaming(agentType, onData) {
|
|
176
|
+
const { spawn } = require('child_process');
|
|
177
|
+
const entry = this.registry.getEntry(agentType);
|
|
178
|
+
if (!entry || !entry.install) {
|
|
179
|
+
throw new Error(`No install definition for agent type: ${agentType}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const installCmd = this._getInstallCommand(entry.install);
|
|
183
|
+
let cmd = this._deriveUninstallCommand(installCmd);
|
|
184
|
+
if (!cmd) {
|
|
185
|
+
throw new Error(`Cannot derive uninstall command for ${agentType}`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Add verbose logging for npm so user sees progress on stderr
|
|
189
|
+
if (cmd.includes('npm uninstall') && !cmd.includes('--loglevel')) {
|
|
190
|
+
cmd = cmd.replace('npm uninstall', 'npm uninstall --loglevel=verbose');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (onData) onData(`$ ${cmd}\n\n`);
|
|
194
|
+
|
|
195
|
+
const env = this._buildShellEnv();
|
|
196
|
+
const shell = process.platform === 'win32'
|
|
197
|
+
? (process.env.ComSpec || 'C:\\Windows\\System32\\cmd.exe')
|
|
198
|
+
: true;
|
|
199
|
+
|
|
200
|
+
return new Promise((resolve, reject) => {
|
|
201
|
+
const proc = spawn(cmd, [], { shell, env, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
202
|
+
|
|
203
|
+
if (proc.stdout) proc.stdout.setEncoding('utf-8');
|
|
204
|
+
if (proc.stderr) proc.stderr.setEncoding('utf-8');
|
|
205
|
+
|
|
206
|
+
if (proc.stdout) proc.stdout.on('data', (d) => { if (onData) onData(d); });
|
|
207
|
+
if (proc.stderr) proc.stderr.on('data', (d) => { if (onData) onData(d); });
|
|
208
|
+
|
|
209
|
+
proc.on('error', (err) => reject(err));
|
|
210
|
+
proc.on('close', (code) => {
|
|
211
|
+
if (code === 0) {
|
|
212
|
+
this._markUninstalled(agentType);
|
|
213
|
+
if (onData) onData(`\nDone! ${agentType} has been uninstalled.\n`);
|
|
214
|
+
resolve({ success: true, command: cmd });
|
|
215
|
+
} else {
|
|
216
|
+
const msg = `Uninstall failed with exit code ${code}`;
|
|
217
|
+
if (onData) onData(`\n${msg}\n`);
|
|
218
|
+
reject(new Error(msg));
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
118
224
|
/**
|
|
119
225
|
* Get install command for current platform.
|
|
120
226
|
*/
|
|
@@ -223,25 +329,43 @@ class Installer {
|
|
|
223
329
|
} catch {}
|
|
224
330
|
}
|
|
225
331
|
|
|
226
|
-
// -- Shell exec --
|
|
332
|
+
// -- Shell env + exec --
|
|
333
|
+
|
|
334
|
+
_buildShellEnv() {
|
|
335
|
+
const env = { ...process.env };
|
|
336
|
+
const sep = process.platform === 'win32' ? ';' : ':';
|
|
337
|
+
const extraDirs = [];
|
|
338
|
+
try { extraDirs.push(path.dirname(process.execPath)); } catch {}
|
|
339
|
+
if (process.platform === 'win32') {
|
|
340
|
+
const appData = env.APPDATA || '';
|
|
341
|
+
if (appData) extraDirs.push(path.join(appData, 'npm'));
|
|
342
|
+
extraDirs.push(env.ProgramFiles ? path.join(env.ProgramFiles, 'nodejs') : 'C:\\Program Files\\nodejs');
|
|
343
|
+
extraDirs.push(env.SystemRoot ? path.join(env.SystemRoot, 'System32') : 'C:\\Windows\\System32');
|
|
344
|
+
extraDirs.push(env.ProgramFiles ? path.join(env.ProgramFiles, 'Git', 'cmd') : 'C:\\Program Files\\Git\\cmd');
|
|
345
|
+
} else {
|
|
346
|
+
extraDirs.push('/usr/local/bin', '/opt/homebrew/bin');
|
|
347
|
+
}
|
|
348
|
+
for (const d of extraDirs) {
|
|
349
|
+
if (d && !(env.PATH || '').includes(d)) {
|
|
350
|
+
env.PATH = d + sep + (env.PATH || '');
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return env;
|
|
354
|
+
}
|
|
227
355
|
|
|
228
356
|
_execShell(cmd, timeoutMs = 300000) {
|
|
229
357
|
return new Promise((resolve, reject) => {
|
|
230
|
-
const env =
|
|
231
|
-
// Also include Electron's own binary dir
|
|
232
|
-
const execDir = path.dirname(process.execPath);
|
|
233
|
-
if (execDir && !(env.PATH || '').includes(execDir)) {
|
|
234
|
-
const sep = process.platform === 'win32' ? ';' : ':';
|
|
235
|
-
env.PATH = execDir + sep + (env.PATH || '');
|
|
236
|
-
}
|
|
358
|
+
const env = this._buildShellEnv();
|
|
237
359
|
|
|
238
|
-
|
|
239
|
-
|
|
360
|
+
let shell = true;
|
|
361
|
+
if (process.platform === 'win32') {
|
|
362
|
+
shell = env.ComSpec || 'C:\\Windows\\System32\\cmd.exe';
|
|
363
|
+
}
|
|
240
364
|
|
|
241
|
-
exec(
|
|
365
|
+
exec(cmd, {
|
|
242
366
|
encoding: 'utf-8',
|
|
243
367
|
timeout: timeoutMs,
|
|
244
|
-
shell
|
|
368
|
+
shell,
|
|
245
369
|
env,
|
|
246
370
|
}, (error, stdout, stderr) => {
|
|
247
371
|
const output = ((stdout || '') + '\n' + (stderr || '')).trim();
|
package/src/paths.js
CHANGED
|
@@ -36,11 +36,19 @@ function getExtraBinDirs() {
|
|
|
36
36
|
// Common: ~/.local/bin (pipx, user installs)
|
|
37
37
|
_push(dirs, path.join(HOME, '.local', 'bin'));
|
|
38
38
|
|
|
39
|
+
// Also add the directory containing the current node binary
|
|
40
|
+
try {
|
|
41
|
+
const nodeDir = path.dirname(process.execPath);
|
|
42
|
+
if (nodeDir) _push(dirs, nodeDir);
|
|
43
|
+
} catch {}
|
|
44
|
+
|
|
39
45
|
// Filter to existing directories only, deduplicate
|
|
40
46
|
const seen = new Set();
|
|
41
47
|
const currentPATH = process.env.PATH || '';
|
|
42
48
|
return dirs.filter(d => {
|
|
43
|
-
if (!d || seen.has(d)
|
|
49
|
+
if (!d || seen.has(d)) return false;
|
|
50
|
+
// Skip if already in PATH (case-insensitive on Windows)
|
|
51
|
+
if (IS_WINDOWS ? currentPATH.toLowerCase().includes(d.toLowerCase()) : currentPATH.includes(d)) return false;
|
|
44
52
|
seen.add(d);
|
|
45
53
|
try {
|
|
46
54
|
return fs.statSync(d).isDirectory();
|
|
@@ -71,10 +79,14 @@ function getEnhancedEnv(baseEnv) {
|
|
|
71
79
|
}
|
|
72
80
|
if (IS_WINDOWS) {
|
|
73
81
|
// Force UTF-8 output from child processes on non-English Windows locales
|
|
74
|
-
// (prevents GBK/Shift-JIS garbled text in error messages)
|
|
75
82
|
env.PYTHONIOENCODING = env.PYTHONIOENCODING || 'utf-8';
|
|
76
83
|
env.PYTHONUTF8 = env.PYTHONUTF8 || '1';
|
|
77
84
|
env.LANG = env.LANG || 'en_US.UTF-8';
|
|
85
|
+
// Ensure ComSpec points to cmd.exe (Electron may not set it)
|
|
86
|
+
if (!env.ComSpec) {
|
|
87
|
+
const sysRoot = env.SystemRoot || 'C:\\Windows';
|
|
88
|
+
env.ComSpec = path.join(sysRoot, 'System32', 'cmd.exe');
|
|
89
|
+
}
|
|
78
90
|
}
|
|
79
91
|
return env;
|
|
80
92
|
}
|
|
@@ -104,6 +116,10 @@ function _addWindowsPaths(dirs) {
|
|
|
104
116
|
const appData = process.env.APPDATA || '';
|
|
105
117
|
const localAppData = process.env.LOCALAPPDATA || '';
|
|
106
118
|
const programFiles = process.env.ProgramFiles || 'C:\\Program Files';
|
|
119
|
+
const sysRoot = process.env.SystemRoot || 'C:\\Windows';
|
|
120
|
+
|
|
121
|
+
// System32 (cmd.exe, powershell, etc) — Electron may not have it
|
|
122
|
+
_push(dirs, path.join(sysRoot, 'System32'));
|
|
107
123
|
|
|
108
124
|
// npm global bin
|
|
109
125
|
if (appData) _push(dirs, path.join(appData, 'npm'));
|