robot-resources 1.4.1 → 1.5.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.
@@ -1,10 +1,40 @@
1
- import { execFileSync } from 'node:child_process';
1
+ import { spawn } from 'node:child_process';
2
2
  import { readFileSync, writeFileSync } from 'node:fs';
3
3
  import { homedir } from 'node:os';
4
4
  import { join } from 'node:path';
5
5
  import { isOpenClawInstalled, isOpenClawPluginInstalled, getOpenClawAuthMode } from './detect.js';
6
6
  import { stripJson5 } from './json5.js';
7
7
 
8
+ /**
9
+ * Run a command with a heartbeat that prints progress every 5 seconds.
10
+ * Prevents agent sessions from timing out during silent operations.
11
+ */
12
+ function spawnWithHeartbeat(cmd, args, { label, timeout = 30_000 } = {}) {
13
+ return new Promise((resolve, reject) => {
14
+ const proc = spawn(cmd, args, {
15
+ stdio: ['ignore', 'pipe', 'pipe'],
16
+ timeout,
17
+ });
18
+
19
+ let seconds = 0;
20
+ const heartbeat = setInterval(() => {
21
+ seconds += 5;
22
+ process.stdout.write(` ${label}... ${seconds}s\n`);
23
+ }, 5000);
24
+
25
+ proc.on('close', (code) => {
26
+ clearInterval(heartbeat);
27
+ if (code === 0) resolve();
28
+ else reject(new Error(`${cmd} ${args.join(' ')} exited with code ${code}`));
29
+ });
30
+
31
+ proc.on('error', (err) => {
32
+ clearInterval(heartbeat);
33
+ reject(err);
34
+ });
35
+ });
36
+ }
37
+
8
38
  /**
9
39
  * Set robot-resources/auto as the default model in openclaw.json.
10
40
  *
@@ -69,7 +99,7 @@ function registerScraperMcp() {
69
99
 
70
100
  config.mcp.servers['robot-resources-scraper'] = {
71
101
  command: 'npx',
72
- args: ['-y', '@robot-resources/scraper-mcp'],
102
+ args: ['-y', '-p', '@robot-resources/scraper', 'scraper-mcp'],
73
103
  };
74
104
 
75
105
  writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
@@ -93,7 +123,7 @@ function registerScraperMcp() {
93
123
  * OAuth tokens from third-party clients, so HTTP proxy won't work.
94
124
  * - apikey: Plugin is preferred (survives restarts) but proxy also works.
95
125
  */
96
- function configureOpenClaw() {
126
+ async function configureOpenClaw() {
97
127
  const authMode = getOpenClawAuthMode();
98
128
 
99
129
  if (isOpenClawPluginInstalled()) {
@@ -105,8 +135,8 @@ function configureOpenClaw() {
105
135
  }
106
136
 
107
137
  try {
108
- execFileSync('openclaw', ['plugins', 'install', '@robot-resources/openclaw-plugin'], {
109
- stdio: 'ignore',
138
+ await spawnWithHeartbeat('openclaw', ['plugins', 'install', '@robot-resources/openclaw-plugin'], {
139
+ label: 'Installing plugin',
110
140
  timeout: 30_000,
111
141
  });
112
142
 
@@ -117,8 +147,8 @@ function configureOpenClaw() {
117
147
  // Restart the gateway so it picks up the new plugin + config.
118
148
  let gatewayRestarted = false;
119
149
  try {
120
- execFileSync('openclaw', ['gateway', 'restart'], {
121
- stdio: 'ignore',
150
+ await spawnWithHeartbeat('openclaw', ['gateway', 'restart'], {
151
+ label: 'Restarting gateway',
122
152
  timeout: 15_000,
123
153
  });
124
154
  gatewayRestarted = true;
@@ -167,12 +197,12 @@ function configureOpenClaw() {
167
197
  *
168
198
  * Returns array of { name, action, ... } results.
169
199
  */
170
- export function configureToolRouting() {
200
+ export async function configureToolRouting() {
171
201
  const results = [];
172
202
 
173
203
  // OpenClaw
174
204
  if (isOpenClawInstalled()) {
175
- results.push(configureOpenClaw());
205
+ results.push(await configureOpenClaw());
176
206
  }
177
207
 
178
208
  return results;
package/lib/wizard.js CHANGED
@@ -172,7 +172,7 @@ export async function runWizard({ nonInteractive = false } = {}) {
172
172
  blank();
173
173
  step('Configuring AI tools to use Router...');
174
174
 
175
- const toolResults = configureToolRouting();
175
+ const toolResults = await configureToolRouting();
176
176
  results.tools = toolResults;
177
177
 
178
178
  if (toolResults.length === 0) {
@@ -204,9 +204,9 @@ export async function runWizard({ nonInteractive = false } = {}) {
204
204
  // ── Step 4: Scraper Installation ───────────────────────────────────────
205
205
  //
206
206
  // Independent of router. Scraper works even if router failed to install.
207
- // 1. Register scraper-mcp in openclaw.json (if OC is present)
208
- // 2. Pre-cache the scraper-mcp npm package
209
- // 3. Restart gateway so OC picks up the new MCP server
207
+ // 1. Register scraper MCP in openclaw.json (if OC is present)
208
+ // 2. Restart gateway so OC picks up the new MCP server
209
+ // No pre-cache needed scraper is bundled as a CLI dependency
210
210
 
211
211
  blank();
212
212
  step('Installing Scraper...');
@@ -232,26 +232,24 @@ export async function runWizard({ nonInteractive = false } = {}) {
232
232
  }
233
233
  }
234
234
 
235
- // Pre-cache scraper-mcp package so OC doesn't need to download on first use
236
- try {
237
- const { execFileSync: execFs } = await import('node:child_process');
238
- execFs('npx', ['-y', '@robot-resources/scraper-mcp', '--help'], {
239
- stdio: 'ignore',
240
- timeout: 60_000,
241
- });
242
- success('Scraper MCP package cached');
243
- results.scraper = true;
244
- } catch {
245
- warn('Scraper MCP pre-cache failed — will download on first use');
246
- }
235
+ results.scraper = true;
247
236
 
248
237
  // Restart gateway so OC picks up the scraper MCP server
249
238
  if (scraperRegistered) {
250
239
  try {
251
- const { execFileSync: execFs2 } = await import('node:child_process');
252
- execFs2('openclaw', ['gateway', 'restart'], {
253
- stdio: 'ignore',
254
- timeout: 15_000,
240
+ const { spawn: spawnProc } = await import('node:child_process');
241
+ await new Promise((resolve, reject) => {
242
+ const proc = spawnProc('openclaw', ['gateway', 'restart'], {
243
+ stdio: ['ignore', 'pipe', 'pipe'],
244
+ timeout: 15_000,
245
+ });
246
+ let seconds = 0;
247
+ const hb = setInterval(() => {
248
+ seconds += 5;
249
+ process.stdout.write(` Restarting gateway... ${seconds}s\n`);
250
+ }, 5000);
251
+ proc.on('close', (code) => { clearInterval(hb); code === 0 ? resolve() : reject(new Error(`exit ${code}`)); });
252
+ proc.on('error', (err) => { clearInterval(hb); reject(err); });
255
253
  });
256
254
  success('OpenClaw gateway restarted');
257
255
  results.scraper = true;
@@ -267,7 +265,7 @@ export async function runWizard({ nonInteractive = false } = {}) {
267
265
  step('Verifying Scraper MCP starts...');
268
266
  try {
269
267
  const { spawn } = await import('node:child_process');
270
- const proc = spawn('npx', ['-y', '@robot-resources/scraper-mcp'], {
268
+ const proc = spawn('npx', ['-y', '-p', '@robot-resources/scraper', 'scraper-mcp'], {
271
269
  stdio: ['pipe', 'pipe', 'pipe'],
272
270
  timeout: 10_000,
273
271
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "robot-resources",
3
- "version": "1.4.1",
3
+ "version": "1.5.1",
4
4
  "description": "Robot Resources — AI agent runtime tools. One command to install everything.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -18,7 +18,7 @@
18
18
  ],
19
19
  "dependencies": {
20
20
  "@robot-resources/cli-core": "*",
21
- "@robot-resources/scraper": "^0.1.0"
21
+ "@robot-resources/scraper": "^0.2.0"
22
22
  },
23
23
  "devDependencies": {
24
24
  "vitest": "^1.2.0"