robot-resources 1.4.0 → 1.4.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.
@@ -114,10 +114,6 @@ function configureOpenClaw() {
114
114
  // before_model_resolve hook actually fires for every request.
115
115
  const configActivated = activateRouterModel();
116
116
 
117
- // Register scraper-mcp so the agent gets scraper_compress_url
118
- // as a native tool, and the plugin can intercept web_fetch.
119
- const scraperRegistered = registerScraperMcp();
120
-
121
117
  // Restart the gateway so it picks up the new plugin + config.
122
118
  let gatewayRestarted = false;
123
119
  try {
@@ -135,7 +131,6 @@ function configureOpenClaw() {
135
131
  action: 'installed',
136
132
  authMode,
137
133
  configActivated,
138
- scraperRegistered,
139
134
  gatewayRestarted,
140
135
  note: authMode === 'subscription'
141
136
  ? 'Plugin required — subscription OAuth tokens are rejected by Anthropic when proxied via third-party clients.'
@@ -183,5 +178,5 @@ export function configureToolRouting() {
183
178
  return results;
184
179
  }
185
180
 
186
- // Exported for testing
187
- export { stripJson5, configureOpenClaw };
181
+ // Exported for testing and direct use
182
+ export { stripJson5, configureOpenClaw, registerScraperMcp };
package/lib/wizard.js CHANGED
@@ -2,7 +2,7 @@ import { readConfig, writeConfig } from '@robot-resources/cli-core/config.mjs';
2
2
  import { findPython, isPortAvailable, isHeadless } from './detect.js';
3
3
  import { setupRouter, isRouterInstalled, getVenvPythonPath } from './python-bridge.js';
4
4
  import { installService, isServiceRunning, isServiceInstalled } from './service.js';
5
- import { configureToolRouting } from './tool-config.js';
5
+ import { configureToolRouting, registerScraperMcp } from './tool-config.js';
6
6
  import { header, step, success, warn, error, info, blank, summary } from './ui.js';
7
7
  /**
8
8
  * Main setup wizard. Handles the full onboarding flow:
@@ -201,8 +201,104 @@ export async function runWizard({ nonInteractive = false } = {}) {
201
201
  }
202
202
  }
203
203
 
204
- // ── Step 4.5: Healthcheck ─────────────────────────────────────────────
204
+ // ── Step 4: Scraper Installation ───────────────────────────────────────
205
+ //
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
210
+
211
+ blank();
212
+ step('Installing Scraper...');
213
+
214
+ results.scraper = false;
205
215
 
216
+ // Register MCP in openclaw.json
217
+ const scraperRegistered = registerScraperMcp();
218
+ if (scraperRegistered) {
219
+ success('Scraper MCP registered in OpenClaw — scraper_compress_url(url) available');
220
+ } else {
221
+ // Either already registered, or no openclaw.json
222
+ try {
223
+ const { readFileSync: readFs } = await import('node:fs');
224
+ const { join: joinPath } = await import('node:path');
225
+ const { homedir: home } = await import('node:os');
226
+ const ocConfig = JSON.parse(readFs(joinPath(home(), '.openclaw', 'openclaw.json'), 'utf-8'));
227
+ if (ocConfig?.mcp?.servers?.['robot-resources-scraper']) {
228
+ success('Scraper MCP already registered in OpenClaw');
229
+ }
230
+ } catch {
231
+ // No openclaw.json — not on OC, skip
232
+ }
233
+ }
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
+ }
247
+
248
+ // Restart gateway so OC picks up the scraper MCP server
249
+ if (scraperRegistered) {
250
+ try {
251
+ const { execFileSync: execFs2 } = await import('node:child_process');
252
+ execFs2('openclaw', ['gateway', 'restart'], {
253
+ stdio: 'ignore',
254
+ timeout: 15_000,
255
+ });
256
+ success('OpenClaw gateway restarted');
257
+ results.scraper = true;
258
+ } catch {
259
+ // Non-fatal — gateway may not be running
260
+ }
261
+ }
262
+
263
+ // ── Step 4.5: Healthchecks ────────────────────────────────────────────
264
+
265
+ // Scraper MCP: verify the server starts without crashing
266
+ if (results.scraper) {
267
+ step('Verifying Scraper MCP starts...');
268
+ try {
269
+ const { spawn } = await import('node:child_process');
270
+ const proc = spawn('npx', ['-y', '@robot-resources/scraper-mcp'], {
271
+ stdio: ['pipe', 'pipe', 'pipe'],
272
+ timeout: 10_000,
273
+ });
274
+
275
+ const healthy = await new Promise((resolve) => {
276
+ const timer = setTimeout(() => {
277
+ // Server stayed alive for 3s — it's working
278
+ proc.kill();
279
+ resolve(true);
280
+ }, 3000);
281
+
282
+ proc.on('error', () => { clearTimeout(timer); resolve(false); });
283
+ proc.on('exit', (code) => {
284
+ clearTimeout(timer);
285
+ // MCP servers don't exit on their own — if it exited, it crashed
286
+ resolve(code === 0);
287
+ });
288
+ });
289
+
290
+ if (healthy) {
291
+ success('Scraper MCP server healthy');
292
+ } else {
293
+ warn('Scraper MCP server exited unexpectedly');
294
+ results.scraper = false;
295
+ }
296
+ } catch {
297
+ warn('Could not verify Scraper MCP server');
298
+ }
299
+ }
300
+
301
+ // Router: verify it's responding on localhost:3838
206
302
  if (results.service) {
207
303
  blank();
208
304
  step('Verifying Router is responding...');
@@ -236,7 +332,7 @@ export async function runWizard({ nonInteractive = false } = {}) {
236
332
 
237
333
  // ── Summary ─────────────────────────────────────────────────────────────
238
334
 
239
- const somethingInstalled = results.router || results.service;
335
+ const somethingInstalled = results.router || results.service || results.scraper;
240
336
 
241
337
  const lines = [];
242
338
 
@@ -251,7 +347,11 @@ export async function runWizard({ nonInteractive = false } = {}) {
251
347
  lines.push('○ Router not installed (Python 3.10+ required)');
252
348
  }
253
349
 
254
- lines.push('✓ Scraper installed (token compression for web content)');
350
+ if (results.scraper) {
351
+ lines.push('✓ Scraper MCP ready — use scraper_compress_url(url) to compress web content');
352
+ } else {
353
+ lines.push('○ Scraper MCP not configured');
354
+ }
255
355
 
256
356
  summary(lines);
257
357
 
@@ -276,7 +376,7 @@ export async function runWizard({ nonInteractive = false } = {}) {
276
376
  if (results.router) info(' • Router installed in ~/.robot-resources/');
277
377
  if (results.service) info(' • Router registered as a transparent proxy (localhost:3838)');
278
378
  if (results.service) info(' • Reads API keys from requests — no keys stored by Router');
279
- info(' • Scraper installed for token-efficient web compression');
379
+ if (results.scraper) info(' • Scraper MCP configured scraper_compress_url(url) available');
280
380
  blank();
281
381
  const claimLink = results.claimUrl || 'https://robotresources.ai/dashboard';
282
382
  info('Suggested message to your human (COPY THIS EXACTLY):');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "robot-resources",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "description": "Robot Resources — AI agent runtime tools. One command to install everything.",
5
5
  "type": "module",
6
6
  "bin": {