robot-resources 1.15.2 → 1.15.3
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/package.json +7 -49
- package/README.md +0 -104
- package/bin/setup.js +0 -43
- package/lib/auth.mjs +0 -261
- package/lib/config.mjs +0 -55
- package/lib/detect.js +0 -254
- package/lib/health-report.js +0 -130
- package/lib/install-node-shim.js +0 -188
- package/lib/install-python-shim.js +0 -107
- package/lib/install-router-files.js +0 -48
- package/lib/json5.js +0 -16
- package/lib/login.mjs +0 -54
- package/lib/machine-id.js +0 -31
- package/lib/non-oc-wizard.js +0 -615
- package/lib/shell-config.js +0 -183
- package/lib/source-edit-attach.js +0 -469
- package/lib/tool-config.js +0 -504
- package/lib/ui.js +0 -87
- package/lib/uninstall.js +0 -208
- package/lib/venv-detect.js +0 -85
- package/lib/windows-env.js +0 -202
- package/lib/wizard.js +0 -523
package/lib/tool-config.js
DELETED
|
@@ -1,504 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import { createRequire } from 'node:module';
|
|
3
|
-
import { readFileSync, writeFileSync, copyFileSync, cpSync, mkdirSync, existsSync, rmSync } from 'node:fs';
|
|
4
|
-
import { homedir } from 'node:os';
|
|
5
|
-
import { join, dirname } from 'node:path';
|
|
6
|
-
import { isOpenClawInstalled, isOpenClawPluginInstalled, isScraperOcPluginInstalled, getOpenClawAuthMode, isClaudeCodeInstalled, isCursorInstalled } from './detect.js';
|
|
7
|
-
import { stripJson5 } from './json5.js';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Read openclaw.json, creating it with a minimal structure if it doesn't exist.
|
|
11
|
-
* Returns parsed config object. Throws on malformed JSON (caller handles).
|
|
12
|
-
*/
|
|
13
|
-
function readOrCreateOpenClawConfig() {
|
|
14
|
-
const configDir = join(homedir(), '.openclaw');
|
|
15
|
-
const configPath = join(configDir, 'openclaw.json');
|
|
16
|
-
|
|
17
|
-
if (!existsSync(configPath)) {
|
|
18
|
-
mkdirSync(configDir, { recursive: true });
|
|
19
|
-
const minimal = {};
|
|
20
|
-
writeFileSync(configPath, JSON.stringify(minimal, null, 2) + '\n', 'utf-8');
|
|
21
|
-
return minimal;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const raw = readFileSync(configPath, 'utf-8');
|
|
25
|
-
return JSON.parse(stripJson5(raw));
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Trust the Robot Resources plugin in OpenClaw config.
|
|
30
|
-
*
|
|
31
|
-
* Adds "robot-resources-router" to plugins.allow so OpenClaw loads it without
|
|
32
|
-
* provenance warnings. The plugin's before_model_resolve hook intercepts
|
|
33
|
-
* ALL LLM calls regardless of the default model — no need to change the
|
|
34
|
-
* default model (which causes LiveSessionModelSwitchError in OC).
|
|
35
|
-
*
|
|
36
|
-
* Returns true if the config was updated, false otherwise.
|
|
37
|
-
*/
|
|
38
|
-
function trustPlugin() {
|
|
39
|
-
const configPath = join(homedir(), '.openclaw', 'openclaw.json');
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
const config = readOrCreateOpenClawConfig();
|
|
43
|
-
|
|
44
|
-
if (!config.plugins) config.plugins = {};
|
|
45
|
-
if (!Array.isArray(config.plugins.allow)) config.plugins.allow = [];
|
|
46
|
-
|
|
47
|
-
if (config.plugins.allow.includes('robot-resources-router')) {
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
config.plugins.allow.push('robot-resources-router');
|
|
52
|
-
|
|
53
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
54
|
-
return true;
|
|
55
|
-
} catch {
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Register scraper-mcp as an MCP server in openclaw.json.
|
|
62
|
-
*
|
|
63
|
-
* This makes scraper_compress_url and scraper_crawl_url available
|
|
64
|
-
* as native tools in OpenClaw. The plugin's before_tool_call hook
|
|
65
|
-
* then intercepts web_fetch to route through the scraper by default.
|
|
66
|
-
*
|
|
67
|
-
* Returns true if the config was updated, false otherwise.
|
|
68
|
-
*/
|
|
69
|
-
function registerScraperMcp() {
|
|
70
|
-
const configPath = join(homedir(), '.openclaw', 'openclaw.json');
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
const config = readOrCreateOpenClawConfig();
|
|
74
|
-
|
|
75
|
-
if (!config.mcp) config.mcp = {};
|
|
76
|
-
if (!config.mcp.servers) config.mcp.servers = {};
|
|
77
|
-
|
|
78
|
-
// Already registered
|
|
79
|
-
if (config.mcp.servers['robot-resources-scraper']) {
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
config.mcp.servers['robot-resources-scraper'] = {
|
|
84
|
-
command: 'npx',
|
|
85
|
-
args: ['-y', '-p', '@robot-resources/scraper', 'scraper-mcp'],
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
89
|
-
return true;
|
|
90
|
-
} catch {
|
|
91
|
-
// Config missing or malformed — non-fatal
|
|
92
|
-
return false;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Copy the bundled plugin files to ~/.openclaw/extensions/robot-resources-router/.
|
|
98
|
-
*
|
|
99
|
-
* The plugin ships as a CLI dependency (@robot-resources/router — the
|
|
100
|
-
* router IS the OC plugin in the in-process architecture).
|
|
101
|
-
* Instead of spawning `openclaw plugins install` (30s npm overhead),
|
|
102
|
-
* we copy files directly. Same destination, same result.
|
|
103
|
-
*
|
|
104
|
-
* The plugin is a thin shim (index.js) that imports the rest
|
|
105
|
-
* of its code from ./lib/*.js — copy the lib/ directory too, or the shim
|
|
106
|
-
* fails to load with MODULE_NOT_FOUND.
|
|
107
|
-
*/
|
|
108
|
-
function installPluginFiles() {
|
|
109
|
-
const require = createRequire(import.meta.url);
|
|
110
|
-
const pluginPkgPath = require.resolve('@robot-resources/router/package.json');
|
|
111
|
-
const pluginDir = dirname(pluginPkgPath);
|
|
112
|
-
|
|
113
|
-
const targetDir = join(homedir(), '.openclaw', 'extensions', 'robot-resources-router');
|
|
114
|
-
mkdirSync(targetDir, { recursive: true });
|
|
115
|
-
|
|
116
|
-
for (const file of ['index.js', 'openclaw.plugin.json', 'package.json']) {
|
|
117
|
-
copyFileSync(join(pluginDir, file), join(targetDir, file));
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Copy lib/ recursively. Clear the destination first so files removed in
|
|
121
|
-
// a new version don't linger from a previous install.
|
|
122
|
-
const srcLib = join(pluginDir, 'lib');
|
|
123
|
-
const dstLib = join(targetDir, 'lib');
|
|
124
|
-
if (existsSync(srcLib)) {
|
|
125
|
-
rmSync(dstLib, { recursive: true, force: true });
|
|
126
|
-
cpSync(srcLib, dstLib, { recursive: true });
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Register the plugin in openclaw.json so OC loads it on gateway start.
|
|
132
|
-
* Adds plugins.entries.robot-resources-router = { enabled: true }.
|
|
133
|
-
*/
|
|
134
|
-
function registerPluginEntry() {
|
|
135
|
-
const configPath = join(homedir(), '.openclaw', 'openclaw.json');
|
|
136
|
-
|
|
137
|
-
try {
|
|
138
|
-
const config = readOrCreateOpenClawConfig();
|
|
139
|
-
|
|
140
|
-
if (!config.plugins) config.plugins = {};
|
|
141
|
-
if (!config.plugins.entries) config.plugins.entries = {};
|
|
142
|
-
|
|
143
|
-
// Already registered
|
|
144
|
-
if (config.plugins.entries['robot-resources-router']) return;
|
|
145
|
-
|
|
146
|
-
config.plugins.entries['robot-resources-router'] = { enabled: true };
|
|
147
|
-
|
|
148
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
149
|
-
} catch {
|
|
150
|
-
// Non-fatal — plugin may still auto-load from extensions dir
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Trust the scraper OC plugin in OpenClaw config.
|
|
156
|
-
*
|
|
157
|
-
* Adds "robot-resources-scraper-oc-plugin" to plugins.allow so OpenClaw
|
|
158
|
-
* loads it without provenance warnings. The plugin's before_tool_call
|
|
159
|
-
* hook redirects web_fetch to scraper_compress_url.
|
|
160
|
-
*
|
|
161
|
-
* Returns true if the config was updated, false otherwise.
|
|
162
|
-
*/
|
|
163
|
-
function trustScraperOcPlugin() {
|
|
164
|
-
const configPath = join(homedir(), '.openclaw', 'openclaw.json');
|
|
165
|
-
|
|
166
|
-
try {
|
|
167
|
-
const config = readOrCreateOpenClawConfig();
|
|
168
|
-
|
|
169
|
-
if (!config.plugins) config.plugins = {};
|
|
170
|
-
if (!Array.isArray(config.plugins.allow)) config.plugins.allow = [];
|
|
171
|
-
|
|
172
|
-
if (config.plugins.allow.includes('robot-resources-scraper-oc-plugin')) {
|
|
173
|
-
return false;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
config.plugins.allow.push('robot-resources-scraper-oc-plugin');
|
|
177
|
-
|
|
178
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
179
|
-
return true;
|
|
180
|
-
} catch {
|
|
181
|
-
return false;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Copy the bundled scraper OC plugin files to
|
|
187
|
-
* ~/.openclaw/extensions/robot-resources-scraper-oc-plugin/.
|
|
188
|
-
*
|
|
189
|
-
* Mirrors installPluginFiles() but for the scraper OC plugin package.
|
|
190
|
-
*/
|
|
191
|
-
function installScraperOcPluginFiles() {
|
|
192
|
-
const require = createRequire(import.meta.url);
|
|
193
|
-
// OC plugin lives as a subfolder inside the scraper package post-consolidation.
|
|
194
|
-
const scraperPkgPath = require.resolve('@robot-resources/scraper/package.json');
|
|
195
|
-
const pluginDir = join(dirname(scraperPkgPath), 'oc-plugin');
|
|
196
|
-
|
|
197
|
-
const targetDir = join(homedir(), '.openclaw', 'extensions', 'robot-resources-scraper-oc-plugin');
|
|
198
|
-
mkdirSync(targetDir, { recursive: true });
|
|
199
|
-
|
|
200
|
-
for (const file of ['index.js', 'openclaw.plugin.json', 'package.json']) {
|
|
201
|
-
copyFileSync(join(pluginDir, file), join(targetDir, file));
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Copy lib/ recursively. Clear destination first so files removed in
|
|
205
|
-
// a new version don't linger from a previous install.
|
|
206
|
-
const srcLib = join(pluginDir, 'lib');
|
|
207
|
-
const dstLib = join(targetDir, 'lib');
|
|
208
|
-
if (existsSync(srcLib)) {
|
|
209
|
-
rmSync(dstLib, { recursive: true, force: true });
|
|
210
|
-
cpSync(srcLib, dstLib, { recursive: true });
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Register the scraper OC plugin in openclaw.json so OC loads it on
|
|
216
|
-
* gateway start. Adds plugins.entries['robot-resources-scraper-oc-plugin'] = { enabled: true }.
|
|
217
|
-
*/
|
|
218
|
-
function registerScraperOcPluginEntry() {
|
|
219
|
-
const configPath = join(homedir(), '.openclaw', 'openclaw.json');
|
|
220
|
-
|
|
221
|
-
try {
|
|
222
|
-
const config = readOrCreateOpenClawConfig();
|
|
223
|
-
|
|
224
|
-
if (!config.plugins) config.plugins = {};
|
|
225
|
-
if (!config.plugins.entries) config.plugins.entries = {};
|
|
226
|
-
|
|
227
|
-
if (config.plugins.entries['robot-resources-scraper-oc-plugin']) return;
|
|
228
|
-
|
|
229
|
-
config.plugins.entries['robot-resources-scraper-oc-plugin'] = { enabled: true };
|
|
230
|
-
|
|
231
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
232
|
-
} catch {
|
|
233
|
-
// Non-fatal — plugin may still auto-load from extensions dir
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Configure OpenClaw to route through Robot Resources Router.
|
|
239
|
-
*
|
|
240
|
-
* Copies the bundled @robot-resources/router files into
|
|
241
|
-
* ~/.openclaw/extensions/. The plugin uses before_model_resolve to
|
|
242
|
-
* override the provider — survives gateway restarts because it
|
|
243
|
-
* lives in ~/.openclaw/extensions/, not in openclaw.json.
|
|
244
|
-
*
|
|
245
|
-
* Auth mode detection:
|
|
246
|
-
* - subscription (OAuth token): Plugin is REQUIRED. Anthropic rejects
|
|
247
|
-
* OAuth tokens from third-party clients, so HTTP proxy won't work.
|
|
248
|
-
* - apikey: Plugin is preferred (survives restarts) but proxy also works.
|
|
249
|
-
*/
|
|
250
|
-
function configureOpenClaw() {
|
|
251
|
-
const authMode = getOpenClawAuthMode();
|
|
252
|
-
|
|
253
|
-
const routerWasInstalled = isOpenClawPluginInstalled();
|
|
254
|
-
const scraperWasInstalled = isScraperOcPluginInstalled();
|
|
255
|
-
|
|
256
|
-
if (routerWasInstalled && scraperWasInstalled) {
|
|
257
|
-
return {
|
|
258
|
-
name: 'OpenClaw',
|
|
259
|
-
action: 'already_configured',
|
|
260
|
-
authMode,
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
try {
|
|
265
|
-
let configActivated = false;
|
|
266
|
-
|
|
267
|
-
if (!routerWasInstalled) {
|
|
268
|
-
installPluginFiles();
|
|
269
|
-
registerPluginEntry();
|
|
270
|
-
configActivated = trustPlugin();
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
if (!scraperWasInstalled) {
|
|
274
|
-
installScraperOcPluginFiles();
|
|
275
|
-
registerScraperOcPluginEntry();
|
|
276
|
-
// OR-combine so configActivated reflects "any plugin entry was added to allow".
|
|
277
|
-
configActivated = trustScraperOcPlugin() || configActivated;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
return {
|
|
281
|
-
name: 'OpenClaw',
|
|
282
|
-
action: 'installed',
|
|
283
|
-
authMode,
|
|
284
|
-
configActivated,
|
|
285
|
-
note: authMode === 'subscription'
|
|
286
|
-
? 'Plugin required — subscription OAuth tokens are rejected by Anthropic when proxied via third-party clients.'
|
|
287
|
-
: undefined,
|
|
288
|
-
};
|
|
289
|
-
} catch {
|
|
290
|
-
// Plugin file copy failed — fall back to instructions
|
|
291
|
-
const instructions = [
|
|
292
|
-
'Could not auto-install plugin. Install manually:',
|
|
293
|
-
' openclaw plugins install @robot-resources/router',
|
|
294
|
-
];
|
|
295
|
-
|
|
296
|
-
if (authMode === 'subscription') {
|
|
297
|
-
instructions.push(
|
|
298
|
-
'IMPORTANT: Subscription mode detected (OAuth token).',
|
|
299
|
-
'The plugin is required — HTTP proxy cannot forward OAuth tokens.',
|
|
300
|
-
'Anthropic rejects OAuth tokens from third-party clients.',
|
|
301
|
-
);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
instructions.push('Docs: https://github.com/robot-resources/packages');
|
|
305
|
-
|
|
306
|
-
return {
|
|
307
|
-
name: 'OpenClaw',
|
|
308
|
-
action: 'instructions',
|
|
309
|
-
authMode,
|
|
310
|
-
instructions,
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* Generate copy-pasteable SDK configuration instructions.
|
|
317
|
-
*
|
|
318
|
-
* Returned when no AI tools are auto-detected. Gives the developer
|
|
319
|
-
* exactly what they need to point their SDK at the router manually.
|
|
320
|
-
*/
|
|
321
|
-
function printManualInstructions() {
|
|
322
|
-
return {
|
|
323
|
-
name: 'Manual Configuration',
|
|
324
|
-
action: 'instructions',
|
|
325
|
-
instructions: [
|
|
326
|
-
'No AI tools detected for auto-configuration.',
|
|
327
|
-
'Point your SDK at the Router by setting the base URL:',
|
|
328
|
-
'',
|
|
329
|
-
' # OpenAI SDK / compatible clients (include /v1 in the URL)',
|
|
330
|
-
' export OPENAI_BASE_URL=http://localhost:3838/v1',
|
|
331
|
-
' # OpenAI(base_url="http://localhost:3838/v1")',
|
|
332
|
-
'',
|
|
333
|
-
' # Anthropic SDK (NO /v1 — the SDK appends /v1/messages itself)',
|
|
334
|
-
' export ANTHROPIC_BASE_URL=http://localhost:3838',
|
|
335
|
-
' # Anthropic(base_url="http://localhost:3838")',
|
|
336
|
-
'',
|
|
337
|
-
' # Google / Gemini: native SDK is NOT supported via base_url.',
|
|
338
|
-
' # Use the OpenAI-compatible client with a Gemini model name:',
|
|
339
|
-
' # OpenAI(base_url="http://localhost:3838/v1")',
|
|
340
|
-
' # model = "gemini-2.5-flash"',
|
|
341
|
-
'',
|
|
342
|
-
'Docs: https://github.com/robot-resources/packages',
|
|
343
|
-
],
|
|
344
|
-
};
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* Configure Claude Code to use the Router as an MCP server.
|
|
349
|
-
*
|
|
350
|
-
* Writes a robot-resources-router entry to ~/.claude/settings.json
|
|
351
|
-
* under the mcpServers key. Claude Code reads this on startup.
|
|
352
|
-
*/
|
|
353
|
-
function configureClaudeCode() {
|
|
354
|
-
const configPath = join(homedir(), '.claude', 'settings.json');
|
|
355
|
-
|
|
356
|
-
try {
|
|
357
|
-
let config = {};
|
|
358
|
-
if (existsSync(configPath)) {
|
|
359
|
-
config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
if (!config.mcpServers) config.mcpServers = {};
|
|
363
|
-
|
|
364
|
-
if (config.mcpServers['robot-resources-router']) {
|
|
365
|
-
return { name: 'Claude Code', action: 'already_configured' };
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
config.mcpServers['robot-resources-router'] = {
|
|
369
|
-
command: 'npx',
|
|
370
|
-
args: ['-y', '@robot-resources/router', 'mcp'],
|
|
371
|
-
};
|
|
372
|
-
|
|
373
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
374
|
-
return { name: 'Claude Code', action: 'configured' };
|
|
375
|
-
} catch {
|
|
376
|
-
return {
|
|
377
|
-
name: 'Claude Code',
|
|
378
|
-
action: 'instructions',
|
|
379
|
-
instructions: [
|
|
380
|
-
'Could not auto-configure Claude Code. Add manually to ~/.claude/settings.json:',
|
|
381
|
-
' "mcpServers": { "robot-resources-router": { "command": "npx", "args": ["-y", "@robot-resources/router", "mcp"] } }',
|
|
382
|
-
],
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* Configure Cursor to use the Router as an MCP server.
|
|
389
|
-
*
|
|
390
|
-
* Writes a robot-resources-router entry to ~/.cursor/mcp.json
|
|
391
|
-
* under the mcpServers key. Cursor reads this on startup.
|
|
392
|
-
*/
|
|
393
|
-
function configureCursor() {
|
|
394
|
-
const configPath = join(homedir(), '.cursor', 'mcp.json');
|
|
395
|
-
|
|
396
|
-
try {
|
|
397
|
-
let config = {};
|
|
398
|
-
if (existsSync(configPath)) {
|
|
399
|
-
config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
if (!config.mcpServers) config.mcpServers = {};
|
|
403
|
-
|
|
404
|
-
if (config.mcpServers['robot-resources-router']) {
|
|
405
|
-
return { name: 'Cursor', action: 'already_configured' };
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
config.mcpServers['robot-resources-router'] = {
|
|
409
|
-
command: 'npx',
|
|
410
|
-
args: ['-y', '@robot-resources/router', 'mcp'],
|
|
411
|
-
};
|
|
412
|
-
|
|
413
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
414
|
-
return { name: 'Cursor', action: 'configured' };
|
|
415
|
-
} catch {
|
|
416
|
-
return {
|
|
417
|
-
name: 'Cursor',
|
|
418
|
-
action: 'instructions',
|
|
419
|
-
instructions: [
|
|
420
|
-
'Could not auto-configure Cursor. Add manually to ~/.cursor/mcp.json:',
|
|
421
|
-
' "mcpServers": { "robot-resources-router": { "command": "npx", "args": ["-y", "@robot-resources/router", "mcp"] } }',
|
|
422
|
-
],
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
/**
|
|
428
|
-
* Configure all detected AI tools to route through the Router.
|
|
429
|
-
*
|
|
430
|
-
* Returns array of { name, action, ... } results.
|
|
431
|
-
* When no tools are detected, returns manual SDK instructions.
|
|
432
|
-
*/
|
|
433
|
-
export function configureToolRouting() {
|
|
434
|
-
const results = [];
|
|
435
|
-
|
|
436
|
-
// OpenClaw
|
|
437
|
-
if (isOpenClawInstalled()) {
|
|
438
|
-
results.push(configureOpenClaw());
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// Claude Code
|
|
442
|
-
if (isClaudeCodeInstalled()) {
|
|
443
|
-
results.push(configureClaudeCode());
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// Cursor
|
|
447
|
-
if (isCursorInstalled()) {
|
|
448
|
-
results.push(configureCursor());
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// Fallback: manual SDK instructions when no tools detected
|
|
452
|
-
if (results.length === 0) {
|
|
453
|
-
results.push(printManualInstructions());
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
return results;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
/**
|
|
460
|
-
* Run a command with a heartbeat to keep agent sessions alive.
|
|
461
|
-
* OC kills processes after 5s of no output (noOutputTimeoutMs = 5000).
|
|
462
|
-
* Prints immediately, then every 4s (safely under the 5s threshold).
|
|
463
|
-
*/
|
|
464
|
-
function spawnWithHeartbeat(cmd, args, { label, timeout = 30_000 } = {}) {
|
|
465
|
-
return new Promise((resolve, reject) => {
|
|
466
|
-
const proc = spawn(cmd, args, {
|
|
467
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
468
|
-
timeout,
|
|
469
|
-
});
|
|
470
|
-
|
|
471
|
-
process.stdout.write(` ${label}...\n`);
|
|
472
|
-
let seconds = 0;
|
|
473
|
-
const heartbeat = setInterval(() => {
|
|
474
|
-
seconds += 4;
|
|
475
|
-
process.stdout.write(` ${label}... ${seconds}s\n`);
|
|
476
|
-
}, 4000);
|
|
477
|
-
|
|
478
|
-
proc.on('close', (code) => {
|
|
479
|
-
clearInterval(heartbeat);
|
|
480
|
-
if (code === 0) resolve();
|
|
481
|
-
else reject(new Error(`${cmd} ${args.join(' ')} exited with code ${code}`));
|
|
482
|
-
});
|
|
483
|
-
|
|
484
|
-
proc.on('error', (err) => {
|
|
485
|
-
clearInterval(heartbeat);
|
|
486
|
-
reject(err);
|
|
487
|
-
});
|
|
488
|
-
});
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
/**
|
|
492
|
-
* Restart the OpenClaw gateway so it picks up new plugin + config.
|
|
493
|
-
* Uses heartbeat to keep OC sessions alive during the restart.
|
|
494
|
-
* Telegram survives this restart — tested end-to-end (PR #89).
|
|
495
|
-
*/
|
|
496
|
-
async function restartOpenClawGateway() {
|
|
497
|
-
await spawnWithHeartbeat('openclaw', ['gateway', 'restart'], {
|
|
498
|
-
label: 'Restarting gateway',
|
|
499
|
-
timeout: 15_000,
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
// Exported for testing and direct use
|
|
504
|
-
export { stripJson5, configureOpenClaw, configureClaudeCode, configureCursor, registerScraperMcp, restartOpenClawGateway };
|
package/lib/ui.js
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { createInterface } from 'node:readline';
|
|
2
|
-
|
|
3
|
-
// ANSI color helpers (no dependencies)
|
|
4
|
-
const c = {
|
|
5
|
-
reset: '\x1b[0m',
|
|
6
|
-
bold: '\x1b[1m',
|
|
7
|
-
dim: '\x1b[2m',
|
|
8
|
-
green: '\x1b[32m',
|
|
9
|
-
yellow: '\x1b[33m',
|
|
10
|
-
red: '\x1b[31m',
|
|
11
|
-
cyan: '\x1b[36m',
|
|
12
|
-
orange: '\x1b[38;5;208m',
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export function header() {
|
|
16
|
-
console.log(`\n ${c.orange}${c.bold}██ Robot Resources — Setup${c.reset}\n`);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function step(msg) {
|
|
20
|
-
console.log(` ${c.cyan}→${c.reset} ${msg}`);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function success(msg) {
|
|
24
|
-
console.log(` ${c.green}✓${c.reset} ${msg}`);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function warn(msg) {
|
|
28
|
-
console.log(` ${c.yellow}!${c.reset} ${msg}`);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function error(msg) {
|
|
32
|
-
console.log(` ${c.red}✗${c.reset} ${msg}`);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function info(msg) {
|
|
36
|
-
console.log(` ${c.dim}${msg}${c.reset}`);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function blank() {
|
|
40
|
-
console.log('');
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function summary(lines) {
|
|
44
|
-
console.log(`\n ${c.orange}${c.bold}── Summary ──${c.reset}\n`);
|
|
45
|
-
for (const line of lines) {
|
|
46
|
-
console.log(` ${line}`);
|
|
47
|
-
}
|
|
48
|
-
console.log('');
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Prompt for free-text input (e.g. API keys).
|
|
53
|
-
* Returns the trimmed answer, or empty string if skipped.
|
|
54
|
-
* In non-interactive mode, returns the default value.
|
|
55
|
-
*/
|
|
56
|
-
export function prompt(question, { defaultValue = '', nonInteractive = false } = {}) {
|
|
57
|
-
if (nonInteractive) return Promise.resolve(defaultValue);
|
|
58
|
-
|
|
59
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
60
|
-
|
|
61
|
-
return new Promise((resolve) => {
|
|
62
|
-
rl.question(` ${question}: `, (answer) => {
|
|
63
|
-
rl.close();
|
|
64
|
-
resolve(answer.trim());
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Prompt for yes/no confirmation. Returns true for yes.
|
|
71
|
-
* In non-interactive mode, returns the default value.
|
|
72
|
-
*/
|
|
73
|
-
export function confirm(question, { defaultYes = true, nonInteractive = false } = {}) {
|
|
74
|
-
if (nonInteractive) return Promise.resolve(defaultYes);
|
|
75
|
-
|
|
76
|
-
const hint = defaultYes ? 'Y/n' : 'y/N';
|
|
77
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
78
|
-
|
|
79
|
-
return new Promise((resolve) => {
|
|
80
|
-
rl.question(` ${question} (${hint}): `, (answer) => {
|
|
81
|
-
rl.close();
|
|
82
|
-
const trimmed = answer.trim().toLowerCase();
|
|
83
|
-
if (trimmed === '') resolve(defaultYes);
|
|
84
|
-
else resolve(trimmed === 'y' || trimmed === 'yes');
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
}
|