robot-resources 1.3.8 → 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.
package/README.md CHANGED
@@ -11,7 +11,7 @@ npx robot-resources
11
11
  1. **GitHub OAuth** — authenticates via PKCE, saves API key to `~/.robot-resources/config.json`
12
12
  2. **Router install** — detects Python 3.10+, creates venv, pip installs `robot-resources-router`
13
13
  3. **Service registration** — registers router as launchd (macOS) or systemd (Linux) service
14
- 4. **MCP auto-config** — detects Claude Desktop and Cursor, injects scraper MCP entry
14
+ 4. **Scraper** — installs @robot-resources/scraper for token-efficient web compression
15
15
 
16
16
  ## Adding a new tool
17
17
 
@@ -43,6 +43,43 @@ function activateRouterModel() {
43
43
  }
44
44
  }
45
45
 
46
+ /**
47
+ * Register scraper-mcp as an MCP server in openclaw.json.
48
+ *
49
+ * This makes scraper_compress_url and scraper_crawl_url available
50
+ * as native tools in OpenClaw. The plugin's before_tool_call hook
51
+ * then intercepts web_fetch to route through the scraper by default.
52
+ *
53
+ * Returns true if the config was updated, false otherwise.
54
+ */
55
+ function registerScraperMcp() {
56
+ const configPath = join(homedir(), '.openclaw', 'openclaw.json');
57
+
58
+ try {
59
+ const raw = readFileSync(configPath, 'utf-8');
60
+ const config = JSON.parse(stripJson5(raw));
61
+
62
+ if (!config.mcp) config.mcp = {};
63
+ if (!config.mcp.servers) config.mcp.servers = {};
64
+
65
+ // Already registered
66
+ if (config.mcp.servers['robot-resources-scraper']) {
67
+ return false;
68
+ }
69
+
70
+ config.mcp.servers['robot-resources-scraper'] = {
71
+ command: 'npx',
72
+ args: ['-y', '@robot-resources/scraper-mcp'],
73
+ };
74
+
75
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
76
+ return true;
77
+ } catch {
78
+ // Config missing or malformed — non-fatal
79
+ return false;
80
+ }
81
+ }
82
+
46
83
  /**
47
84
  * Configure OpenClaw to route through Robot Resources Router.
48
85
  *
@@ -141,5 +178,5 @@ export function configureToolRouting() {
141
178
  return results;
142
179
  }
143
180
 
144
- // Exported for testing
145
- 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:
@@ -103,7 +103,7 @@ export async function runWizard({ nonInteractive = false } = {}) {
103
103
  if (!python) {
104
104
  warn('Python 3.10+ not found — skipping Router installation');
105
105
  info('Install Python from https://python.org and re-run this wizard');
106
- info('Scraper and MCP tools work without Python');
106
+ info('Scraper works without Python');
107
107
  } else {
108
108
  info(`Found Python ${python.version} (${python.bin})`);
109
109
  step('Installing Router (this may take a moment)...');
@@ -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.3.8",
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": {
@@ -18,7 +18,7 @@
18
18
  ],
19
19
  "dependencies": {
20
20
  "@robot-resources/cli-core": "*",
21
- "@robot-resources/scraper": "*"
21
+ "@robot-resources/scraper": "^0.1.0"
22
22
  },
23
23
  "devDependencies": {
24
24
  "vitest": "^1.2.0"