robot-resources 1.3.7 → 1.4.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.
- package/README.md +2 -2
- package/lib/detect.js +0 -32
- package/lib/tool-config.js +42 -0
- package/lib/wizard.js +7 -43
- package/package.json +3 -2
- package/lib/mcp-config.js +0 -90
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
|
|
|
@@ -66,8 +66,8 @@ packages/cli/ This package (published as "robot-resources" on npm)
|
|
|
66
66
|
bin/setup.js Entry point
|
|
67
67
|
lib/wizard.js Orchestrator — add new tools here
|
|
68
68
|
lib/service.js launchd + systemd service registration
|
|
69
|
-
lib/mcp-config.js Agent detection + MCP JSON patching
|
|
70
69
|
lib/python-bridge.js Python venv management
|
|
71
70
|
lib/detect.js Environment detection
|
|
71
|
+
lib/tool-config.js OpenClaw plugin + model activation
|
|
72
72
|
lib/ui.js Terminal formatting
|
|
73
73
|
```
|
package/lib/detect.js
CHANGED
|
@@ -26,38 +26,6 @@ export function checkRouterVenv() {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
/**
|
|
30
|
-
* Detect installed AI agents by checking their config file locations.
|
|
31
|
-
*/
|
|
32
|
-
export function detectAgents() {
|
|
33
|
-
const home = homedir();
|
|
34
|
-
const agents = [];
|
|
35
|
-
|
|
36
|
-
const known = [
|
|
37
|
-
{
|
|
38
|
-
name: 'Claude Desktop',
|
|
39
|
-
configPath: process.platform === 'darwin'
|
|
40
|
-
? join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json')
|
|
41
|
-
: join(home, '.config', 'Claude', 'claude_desktop_config.json'),
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
name: 'Cursor',
|
|
45
|
-
configPath: join(home, '.cursor', 'mcp.json'),
|
|
46
|
-
},
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
for (const agent of known) {
|
|
50
|
-
// Check if the config file or its parent directory exists
|
|
51
|
-
const configExists = existsSync(agent.configPath);
|
|
52
|
-
const dirExists = existsSync(join(agent.configPath, '..'));
|
|
53
|
-
if (configExists || dirExists) {
|
|
54
|
-
agents.push({ ...agent, configExists });
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return agents;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
29
|
/**
|
|
62
30
|
* Check if port 3838 is available.
|
|
63
31
|
*/
|
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
|
*
|
|
@@ -77,6 +114,10 @@ function configureOpenClaw() {
|
|
|
77
114
|
// before_model_resolve hook actually fires for every request.
|
|
78
115
|
const configActivated = activateRouterModel();
|
|
79
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
|
+
|
|
80
121
|
// Restart the gateway so it picks up the new plugin + config.
|
|
81
122
|
let gatewayRestarted = false;
|
|
82
123
|
try {
|
|
@@ -94,6 +135,7 @@ function configureOpenClaw() {
|
|
|
94
135
|
action: 'installed',
|
|
95
136
|
authMode,
|
|
96
137
|
configActivated,
|
|
138
|
+
scraperRegistered,
|
|
97
139
|
gatewayRestarted,
|
|
98
140
|
note: authMode === 'subscription'
|
|
99
141
|
? 'Plugin required — subscription OAuth tokens are rejected by Anthropic when proxied via third-party clients.'
|
package/lib/wizard.js
CHANGED
|
@@ -2,16 +2,14 @@ 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 { configureAgentMCP } from './mcp-config.js';
|
|
6
5
|
import { configureToolRouting } from './tool-config.js';
|
|
7
6
|
import { header, step, success, warn, error, info, blank, summary } from './ui.js';
|
|
8
7
|
/**
|
|
9
8
|
* Main setup wizard. Handles the full onboarding flow:
|
|
10
9
|
* 1. Router installation (Python venv + pip)
|
|
11
10
|
* 2. Service registration (launchd/systemd)
|
|
12
|
-
* 3.
|
|
13
|
-
* 4.
|
|
14
|
-
* 5. Dashboard login (optional, at the very end)
|
|
11
|
+
* 3. Tool routing (OpenClaw plugin + model activation)
|
|
12
|
+
* 4. Dashboard link
|
|
15
13
|
*
|
|
16
14
|
* Auth is intentionally LAST. The router works fully without it.
|
|
17
15
|
* Dashboard is for humans — agents don't need it.
|
|
@@ -26,7 +24,6 @@ export async function runWizard({ nonInteractive = false } = {}) {
|
|
|
26
24
|
routerError: null,
|
|
27
25
|
providerKeys: false,
|
|
28
26
|
service: false,
|
|
29
|
-
mcp: [],
|
|
30
27
|
};
|
|
31
28
|
|
|
32
29
|
// ── Step 0: Provision API key (before anything else) ────────────────────
|
|
@@ -106,7 +103,7 @@ export async function runWizard({ nonInteractive = false } = {}) {
|
|
|
106
103
|
if (!python) {
|
|
107
104
|
warn('Python 3.10+ not found — skipping Router installation');
|
|
108
105
|
info('Install Python from https://python.org and re-run this wizard');
|
|
109
|
-
info('Scraper
|
|
106
|
+
info('Scraper works without Python');
|
|
110
107
|
} else {
|
|
111
108
|
info(`Found Python ${python.version} (${python.bin})`);
|
|
112
109
|
step('Installing Router (this may take a moment)...');
|
|
@@ -169,32 +166,7 @@ export async function runWizard({ nonInteractive = false } = {}) {
|
|
|
169
166
|
}
|
|
170
167
|
}
|
|
171
168
|
|
|
172
|
-
// ── Step 3:
|
|
173
|
-
|
|
174
|
-
blank();
|
|
175
|
-
step('Configuring MCP in detected agents...');
|
|
176
|
-
|
|
177
|
-
const mcpResults = configureAgentMCP();
|
|
178
|
-
results.mcp = mcpResults;
|
|
179
|
-
|
|
180
|
-
if (mcpResults.length === 0) {
|
|
181
|
-
info('No MCP-compatible agents detected');
|
|
182
|
-
info('You can manually add MCP servers to your agent config later');
|
|
183
|
-
} else {
|
|
184
|
-
for (const r of mcpResults) {
|
|
185
|
-
if (r.action === 'added') {
|
|
186
|
-
success(`${r.name}: scraper MCP configured`);
|
|
187
|
-
} else if (r.action === 'exists') {
|
|
188
|
-
success(`${r.name}: already configured`);
|
|
189
|
-
} else if (r.action === 'skipped') {
|
|
190
|
-
warn(`${r.name}: skipped (${r.reason})`);
|
|
191
|
-
} else if (r.action === 'error') {
|
|
192
|
-
error(`${r.name}: ${r.reason}`);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// ── Step 4: Tool Routing Configuration ─────────────────────────────────
|
|
169
|
+
// ── Step 3: Tool Routing Configuration ──────────────────────────────────
|
|
198
170
|
|
|
199
171
|
if (results.router) {
|
|
200
172
|
blank();
|
|
@@ -264,8 +236,7 @@ export async function runWizard({ nonInteractive = false } = {}) {
|
|
|
264
236
|
|
|
265
237
|
// ── Summary ─────────────────────────────────────────────────────────────
|
|
266
238
|
|
|
267
|
-
const somethingInstalled = results.router || results.service
|
|
268
|
-
|| mcpResults.some((r) => r.action === 'added' || r.action === 'exists');
|
|
239
|
+
const somethingInstalled = results.router || results.service;
|
|
269
240
|
|
|
270
241
|
const lines = [];
|
|
271
242
|
|
|
@@ -280,12 +251,7 @@ export async function runWizard({ nonInteractive = false } = {}) {
|
|
|
280
251
|
lines.push('○ Router not installed (Python 3.10+ required)');
|
|
281
252
|
}
|
|
282
253
|
|
|
283
|
-
|
|
284
|
-
if (mcpAdded.length > 0) {
|
|
285
|
-
lines.push(`✓ MCP configured in: ${mcpAdded.map((r) => r.name).join(', ')}`);
|
|
286
|
-
} else {
|
|
287
|
-
lines.push('○ No agents configured (add MCP manually if needed)');
|
|
288
|
-
}
|
|
254
|
+
lines.push('✓ Scraper installed (token compression for web content)');
|
|
289
255
|
|
|
290
256
|
summary(lines);
|
|
291
257
|
|
|
@@ -310,9 +276,7 @@ export async function runWizard({ nonInteractive = false } = {}) {
|
|
|
310
276
|
if (results.router) info(' • Router installed in ~/.robot-resources/');
|
|
311
277
|
if (results.service) info(' • Router registered as a transparent proxy (localhost:3838)');
|
|
312
278
|
if (results.service) info(' • Reads API keys from requests — no keys stored by Router');
|
|
313
|
-
for
|
|
314
|
-
info(` • ${r.name} config modified to add Scraper MCP`);
|
|
315
|
-
}
|
|
279
|
+
info(' • Scraper installed for token-efficient web compression');
|
|
316
280
|
blank();
|
|
317
281
|
const claimLink = results.claimUrl || 'https://robotresources.ai/dashboard';
|
|
318
282
|
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.0",
|
|
4
4
|
"description": "Robot Resources — AI agent runtime tools. One command to install everything.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"README.md"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@robot-resources/cli-core": "*"
|
|
20
|
+
"@robot-resources/cli-core": "*",
|
|
21
|
+
"@robot-resources/scraper": "^0.1.0"
|
|
21
22
|
},
|
|
22
23
|
"devDependencies": {
|
|
23
24
|
"vitest": "^1.2.0"
|
package/lib/mcp-config.js
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, copyFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { detectAgents } from './detect.js';
|
|
4
|
-
|
|
5
|
-
const SCRAPER_MCP_ENTRY = {
|
|
6
|
-
command: 'npx',
|
|
7
|
-
args: ['-y', '@robot-resources/scraper-mcp'],
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
const MCP_KEY = 'robot-resources-scraper';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Read a JSON file safely. Returns null on failure.
|
|
14
|
-
*/
|
|
15
|
-
function readJsonSafe(filePath) {
|
|
16
|
-
try {
|
|
17
|
-
return JSON.parse(readFileSync(filePath, 'utf-8'));
|
|
18
|
-
} catch {
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Write JSON with backup. Creates parent directory if needed.
|
|
25
|
-
*/
|
|
26
|
-
function writeJsonSafe(filePath, data) {
|
|
27
|
-
const dir = join(filePath, '..');
|
|
28
|
-
mkdirSync(dir, { recursive: true });
|
|
29
|
-
|
|
30
|
-
// Create backup if file exists
|
|
31
|
-
if (existsSync(filePath)) {
|
|
32
|
-
copyFileSync(filePath, `${filePath}.bak`);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n');
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Configure MCP in all detected agents.
|
|
40
|
-
* Returns array of { name, configPath, action } results.
|
|
41
|
-
*/
|
|
42
|
-
export function configureAgentMCP() {
|
|
43
|
-
const agents = detectAgents();
|
|
44
|
-
const results = [];
|
|
45
|
-
|
|
46
|
-
for (const agent of agents) {
|
|
47
|
-
try {
|
|
48
|
-
let config = agent.configExists ? readJsonSafe(agent.configPath) : {};
|
|
49
|
-
|
|
50
|
-
if (!config) {
|
|
51
|
-
results.push({ name: agent.name, configPath: agent.configPath, action: 'skipped', reason: 'invalid JSON' });
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Ensure mcpServers key exists
|
|
56
|
-
config.mcpServers = config.mcpServers || {};
|
|
57
|
-
|
|
58
|
-
// Skip if already configured
|
|
59
|
-
if (config.mcpServers[MCP_KEY]) {
|
|
60
|
-
results.push({ name: agent.name, configPath: agent.configPath, action: 'exists' });
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Add scraper MCP
|
|
65
|
-
config.mcpServers[MCP_KEY] = SCRAPER_MCP_ENTRY;
|
|
66
|
-
writeJsonSafe(agent.configPath, config);
|
|
67
|
-
results.push({ name: agent.name, configPath: agent.configPath, action: 'added' });
|
|
68
|
-
} catch (err) {
|
|
69
|
-
results.push({ name: agent.name, configPath: agent.configPath, action: 'error', reason: err.message });
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return results;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Remove RR MCP entries from all detected agents.
|
|
78
|
-
*/
|
|
79
|
-
export function removeAgentMCP() {
|
|
80
|
-
const agents = detectAgents();
|
|
81
|
-
|
|
82
|
-
for (const agent of agents) {
|
|
83
|
-
if (!agent.configExists) continue;
|
|
84
|
-
const config = readJsonSafe(agent.configPath);
|
|
85
|
-
if (!config?.mcpServers?.[MCP_KEY]) continue;
|
|
86
|
-
|
|
87
|
-
delete config.mcpServers[MCP_KEY];
|
|
88
|
-
writeJsonSafe(agent.configPath, config);
|
|
89
|
-
}
|
|
90
|
-
}
|