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 +1 -1
- package/lib/tool-config.js +39 -2
- package/lib/wizard.js +106 -6
- package/package.json +2 -2
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. **
|
|
14
|
+
4. **Scraper** — installs @robot-resources/scraper for token-efficient web compression
|
|
15
15
|
|
|
16
16
|
## Adding a new tool
|
|
17
17
|
|
package/lib/tool-config.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
+
"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"
|