@openagents-org/agent-connector 0.2.8 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openagents-org/agent-connector",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "description": "Agent management CLI and library for OpenAgents — install, configure, and run AI coding agents",
5
5
  "main": "src/index.js",
6
6
  "bin": {
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,30 +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 = getEnhancedEnv();
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
- // On Windows, use cmd.exe explicitly with UTF-8 codepage
239
- let shellCmd = cmd;
240
- let shellOpt = true;
360
+ let shell = true;
241
361
  if (process.platform === 'win32') {
242
- shellCmd = `cmd.exe /C "chcp 65001 >nul & ${cmd}"`;
243
- shellOpt = false; // we're specifying the shell ourselves
362
+ shell = env.ComSpec || 'C:\\Windows\\System32\\cmd.exe';
244
363
  }
245
364
 
246
- exec(shellCmd, {
365
+ exec(cmd, {
247
366
  encoding: 'utf-8',
248
367
  timeout: timeoutMs,
249
- shell: shellOpt,
368
+ shell,
250
369
  env,
251
370
  }, (error, stdout, stderr) => {
252
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) || currentPATH.includes(d)) return false;
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'));