robot-resources 1.4.0 → 1.5.0

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.
@@ -69,7 +69,7 @@ function registerScraperMcp() {
69
69
 
70
70
  config.mcp.servers['robot-resources-scraper'] = {
71
71
  command: 'npx',
72
- args: ['-y', '@robot-resources/scraper-mcp'],
72
+ args: ['-y', '-p', '@robot-resources/scraper', 'scraper-mcp'],
73
73
  };
74
74
 
75
75
  writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
@@ -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,93 @@ 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. Restart gateway so OC picks up the new MCP server
209
+ // No pre-cache needed — scraper is bundled as a CLI dependency
210
+
211
+ blank();
212
+ step('Installing Scraper...');
213
+
214
+ results.scraper = false;
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
+ }
205
234
 
235
+ results.scraper = true;
236
+
237
+ // Restart gateway so OC picks up the scraper MCP server
238
+ if (scraperRegistered) {
239
+ try {
240
+ const { execFileSync: execFs2 } = await import('node:child_process');
241
+ execFs2('openclaw', ['gateway', 'restart'], {
242
+ stdio: 'ignore',
243
+ timeout: 15_000,
244
+ });
245
+ success('OpenClaw gateway restarted');
246
+ results.scraper = true;
247
+ } catch {
248
+ // Non-fatal — gateway may not be running
249
+ }
250
+ }
251
+
252
+ // ── Step 4.5: Healthchecks ────────────────────────────────────────────
253
+
254
+ // Scraper MCP: verify the server starts without crashing
255
+ if (results.scraper) {
256
+ step('Verifying Scraper MCP starts...');
257
+ try {
258
+ const { spawn } = await import('node:child_process');
259
+ const proc = spawn('npx', ['-y', '-p', '@robot-resources/scraper', 'scraper-mcp'], {
260
+ stdio: ['pipe', 'pipe', 'pipe'],
261
+ timeout: 10_000,
262
+ });
263
+
264
+ const healthy = await new Promise((resolve) => {
265
+ const timer = setTimeout(() => {
266
+ // Server stayed alive for 3s — it's working
267
+ proc.kill();
268
+ resolve(true);
269
+ }, 3000);
270
+
271
+ proc.on('error', () => { clearTimeout(timer); resolve(false); });
272
+ proc.on('exit', (code) => {
273
+ clearTimeout(timer);
274
+ // MCP servers don't exit on their own — if it exited, it crashed
275
+ resolve(code === 0);
276
+ });
277
+ });
278
+
279
+ if (healthy) {
280
+ success('Scraper MCP server healthy');
281
+ } else {
282
+ warn('Scraper MCP server exited unexpectedly');
283
+ results.scraper = false;
284
+ }
285
+ } catch {
286
+ warn('Could not verify Scraper MCP server');
287
+ }
288
+ }
289
+
290
+ // Router: verify it's responding on localhost:3838
206
291
  if (results.service) {
207
292
  blank();
208
293
  step('Verifying Router is responding...');
@@ -236,7 +321,7 @@ export async function runWizard({ nonInteractive = false } = {}) {
236
321
 
237
322
  // ── Summary ─────────────────────────────────────────────────────────────
238
323
 
239
- const somethingInstalled = results.router || results.service;
324
+ const somethingInstalled = results.router || results.service || results.scraper;
240
325
 
241
326
  const lines = [];
242
327
 
@@ -251,7 +336,11 @@ export async function runWizard({ nonInteractive = false } = {}) {
251
336
  lines.push('○ Router not installed (Python 3.10+ required)');
252
337
  }
253
338
 
254
- lines.push('✓ Scraper installed (token compression for web content)');
339
+ if (results.scraper) {
340
+ lines.push('✓ Scraper MCP ready — use scraper_compress_url(url) to compress web content');
341
+ } else {
342
+ lines.push('○ Scraper MCP not configured');
343
+ }
255
344
 
256
345
  summary(lines);
257
346
 
@@ -276,7 +365,7 @@ export async function runWizard({ nonInteractive = false } = {}) {
276
365
  if (results.router) info(' • Router installed in ~/.robot-resources/');
277
366
  if (results.service) info(' • Router registered as a transparent proxy (localhost:3838)');
278
367
  if (results.service) info(' • Reads API keys from requests — no keys stored by Router');
279
- info(' • Scraper installed for token-efficient web compression');
368
+ if (results.scraper) info(' • Scraper MCP configured scraper_compress_url(url) available');
280
369
  blank();
281
370
  const claimLink = results.claimUrl || 'https://robotresources.ai/dashboard';
282
371
  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.5.0",
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"