@openagents-org/agent-connector 0.2.9 → 0.3.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/installer.js +69 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openagents-org/agent-connector",
3
- "version": "0.2.9",
3
+ "version": "0.3.1",
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
@@ -84,11 +84,17 @@ class Installer {
84
84
  throw new Error(`No install definition for agent type: ${agentType}`);
85
85
  }
86
86
 
87
- const cmd = this._getInstallCommand(entry.install);
87
+ let cmd = this._getInstallCommand(entry.install);
88
88
  if (!cmd) {
89
89
  throw new Error(`No install command for ${agentType} on ${Installer.platform()}`);
90
90
  }
91
91
 
92
+ // Use bundled node/npm if system npm not available
93
+ if (cmd.startsWith('npm install')) {
94
+ const args = cmd.replace('npm install', 'install');
95
+ cmd = this._resolveNpmCommand(args);
96
+ }
97
+
92
98
  const output = await this._execShell(cmd);
93
99
  this._markInstalled(agentType);
94
100
  return { success: true, output };
@@ -107,14 +113,18 @@ class Installer {
107
113
  throw new Error(`No install definition for agent type: ${agentType}`);
108
114
  }
109
115
 
110
- let cmd = this._getInstallCommand(entry.install);
111
- if (!cmd) {
116
+ let rawCmd = this._getInstallCommand(entry.install);
117
+ if (!rawCmd) {
112
118
  throw new Error(`No install command for ${agentType} on ${Installer.platform()}`);
113
119
  }
114
120
 
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');
121
+ // Resolve npm to use bundled node if system npm is not available
122
+ let cmd = rawCmd;
123
+ if (rawCmd.startsWith('npm install')) {
124
+ const args = rawCmd.replace('npm install', 'install --loglevel=verbose');
125
+ cmd = this._resolveNpmCommand(args);
126
+ } else if (rawCmd.startsWith('pip install') || rawCmd.startsWith('pipx install')) {
127
+ cmd = rawCmd; // pip commands stay as-is
118
128
  }
119
129
 
120
130
  if (onData) onData(`$ ${cmd}\n\n`);
@@ -180,14 +190,16 @@ class Installer {
180
190
  }
181
191
 
182
192
  const installCmd = this._getInstallCommand(entry.install);
183
- let cmd = this._deriveUninstallCommand(installCmd);
184
- if (!cmd) {
193
+ let rawCmd = this._deriveUninstallCommand(installCmd);
194
+ if (!rawCmd) {
185
195
  throw new Error(`Cannot derive uninstall command for ${agentType}`);
186
196
  }
187
197
 
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');
198
+ // Resolve npm to use bundled node if system npm is not available
199
+ let cmd = rawCmd;
200
+ if (rawCmd.startsWith('npm uninstall')) {
201
+ const args = rawCmd.replace('npm uninstall', 'uninstall --loglevel=verbose');
202
+ cmd = this._resolveNpmCommand(args);
191
203
  }
192
204
 
193
205
  if (onData) onData(`$ ${cmd}\n\n`);
@@ -353,6 +365,52 @@ class Installer {
353
365
  return env;
354
366
  }
355
367
 
368
+ /**
369
+ * Resolve the npm CLI path. Prefers system npm, falls back to bundled
370
+ * npm module run via Electron's node. Works on machines without Node.js.
371
+ */
372
+ _resolveNpmCommand(args) {
373
+ // 1. Try system npm
374
+ const { whichBinary } = require('./paths');
375
+ const systemNpm = whichBinary('npm');
376
+ if (systemNpm) return `"${systemNpm}" ${args}`;
377
+
378
+ // 2. Find bundled npm-cli.js (npm is a dependency of the Launcher)
379
+ const nodeExe = process.execPath;
380
+ const candidates = [];
381
+
382
+ // Try require.resolve first — works when npm is in node_modules
383
+ try {
384
+ candidates.push(require.resolve('npm/bin/npm-cli.js'));
385
+ } catch {}
386
+
387
+ // Search common locations relative to the app
388
+ const searchRoots = [
389
+ path.join(path.dirname(nodeExe), 'resources', 'app'), // packaged Electron
390
+ path.join(path.dirname(nodeExe), 'resources', 'app.asar.unpacked'), // asar unpacked
391
+ path.join(path.dirname(nodeExe), '..'), // portable exe temp dir
392
+ process.cwd(), // dev mode
393
+ ];
394
+ for (const root of searchRoots) {
395
+ candidates.push(path.join(root, 'node_modules', 'npm', 'bin', 'npm-cli.js'));
396
+ }
397
+
398
+ // Also check system node_modules
399
+ candidates.push(path.join(path.dirname(nodeExe), '..', 'lib', 'node_modules', 'npm', 'bin', 'npm-cli.js'));
400
+ candidates.push(path.join(path.dirname(nodeExe), 'node_modules', 'npm', 'bin', 'npm-cli.js'));
401
+
402
+ for (const p of candidates) {
403
+ try {
404
+ if (p && fs.existsSync(p)) {
405
+ return `"${nodeExe}" "${p}" ${args}`;
406
+ }
407
+ } catch {}
408
+ }
409
+
410
+ // 3. Last resort
411
+ return `npm ${args}`;
412
+ }
413
+
356
414
  _execShell(cmd, timeoutMs = 300000) {
357
415
  return new Promise((resolve, reject) => {
358
416
  const env = this._buildShellEnv();