robot-resources 1.11.0 → 1.11.2
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 +53 -51
- package/bin/setup.js +26 -14
- package/lib/non-oc-wizard.js +15 -6
- package/lib/tool-config.js +2 -2
- package/lib/uninstall.js +100 -0
- package/lib/wizard.js +95 -21
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,88 +1,90 @@
|
|
|
1
|
-
#
|
|
1
|
+
# robot-resources
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
|
|
5
|
-
Two products for any software that makes LLM API calls — chatbots, RAG pipelines, AI-powered apps, agent runtimes. Both run locally, both free.
|
|
6
|
-
|
|
7
|
-
**Router** — Intelligent LLM routing proxy. Classifies each prompt by task type, routes to the cheapest model that qualifies. 60-90% cost savings with zero quality loss.
|
|
8
|
-
|
|
9
|
-
**Scraper** — Token compression for web content. Fetches any URL, strips noise, returns clean markdown. Median 91% token reduction.
|
|
10
|
-
|
|
11
|
-
## Install
|
|
3
|
+
> One command to install Robot Resources tools for any agent stack.
|
|
12
4
|
|
|
13
5
|
```bash
|
|
14
6
|
npx robot-resources
|
|
15
7
|
```
|
|
16
8
|
|
|
17
|
-
|
|
9
|
+
The wizard detects what you're building and walks you through the right setup — OpenClaw plugin install, JS library path, Python SDK path, MCP config for Cursor / Claude Code, or just the docs URL.
|
|
18
10
|
|
|
19
|
-
## What
|
|
11
|
+
## What the wizard does
|
|
20
12
|
|
|
21
|
-
1. **
|
|
22
|
-
2. **
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
13
|
+
1. **Provisions an anonymous API key** via `POST /v1/auth/signup` (saved to `~/.robot-resources/config.json`). Optional — routing works without it; the key just lights up the dashboard at robotresources.ai/dashboard.
|
|
14
|
+
2. **Detects your stack**:
|
|
15
|
+
- OpenClaw installed (`~/.openclaw/`) → installs the router plugin (in-process HTTP server) + scraper OC plugin, patches `openclaw.json`, restarts the gateway. **No Python, no daemon, no system service.**
|
|
16
|
+
- Non-OC + cwd has `package.json` with LangChain/LangGraph/Mastra → preselects "JS/TS agent."
|
|
17
|
+
- Non-OC + cwd has `requirements.txt` / `pyproject.toml` → preselects "Python agent."
|
|
18
|
+
- Non-OC + Cursor or Claude Code installed → preselects "MCP tool."
|
|
19
|
+
3. **Runs the chosen path**:
|
|
20
|
+
- **JS/TS agent** → prints `npm install @robot-resources/router` + `import { routePrompt }` example
|
|
21
|
+
- **Python agent** → prints `pip install robot-resources` + `from robot_resources.router import route` example, plus an httpx fallback if you'd rather skip the SDK
|
|
22
|
+
- **Cursor / Claude Code** → writes the scraper MCP config into `~/.cursor/mcp.json` / `~/.claude/settings.json`
|
|
23
|
+
- **Docs** → prints the URL + exits
|
|
24
|
+
- **Install OpenClaw first** → redirect message + exits
|
|
26
25
|
|
|
27
|
-
##
|
|
26
|
+
## Flags
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
-
|
|
34
|
-
|
|
35
|
-
|
|
28
|
+
```
|
|
29
|
+
--for=<target> langchain | python | cursor | claude-code | docs
|
|
30
|
+
Skip the prompt and run that path directly.
|
|
31
|
+
Required for non-TTY contexts (CI, piped, etc.)
|
|
32
|
+
--non-interactive Treat as CI run regardless of TTY state
|
|
33
|
+
--yes / -y Same as --non-interactive
|
|
34
|
+
```
|
|
36
35
|
|
|
37
|
-
|
|
36
|
+
Without flags in a non-TTY context, the wizard prints the `--for=` hint and exits cleanly — never blocks waiting for stdin.
|
|
38
37
|
|
|
39
|
-
|
|
38
|
+
## Pre-set the API key
|
|
40
39
|
|
|
41
|
-
|
|
42
|
-
- Content-aware token estimation
|
|
43
|
-
- 3-tier fetch: fast, stealth (TLS fingerprint), render (headless browser)
|
|
44
|
-
- Multi-page BFS crawl with robots.txt compliance
|
|
45
|
-
- Median 91% token reduction per page
|
|
40
|
+
For fleets or CI:
|
|
46
41
|
|
|
47
|
-
|
|
42
|
+
```bash
|
|
43
|
+
export RR_API_KEY=rr_live_... # skip signup, use this key
|
|
44
|
+
npx robot-resources --for=cursor # or whatever path applies
|
|
45
|
+
```
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
|-------|-------------|--------|
|
|
51
|
-
| **OpenClaw** | Plugin (auto-install) | Verified |
|
|
52
|
-
| **Claude Code** | MCP server | Verified |
|
|
53
|
-
| **Any OpenAI client** | HTTP proxy (localhost:3838) | Compatible |
|
|
54
|
-
| **Cursor** | MCP server | Compatible |
|
|
55
|
-
| **Windsurf** | MCP server | Compatible |
|
|
47
|
+
## Five paths, one wizard
|
|
56
48
|
|
|
57
|
-
|
|
49
|
+
| Path | What you get | Where |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| OpenClaw plugin | In-process router inside the OC gateway. Auto-routes Anthropic calls to the cheapest capable model. | `~/.openclaw/extensions/robot-resources-router/` |
|
|
52
|
+
| JS/TS agent | `@robot-resources/router/routing` — pure ESM, zero deps, offline keyword classifier. | npm |
|
|
53
|
+
| Python agent | `robot-resources` (singular) — thin httpx client over `/v1/route`. | PyPI |
|
|
54
|
+
| HTTP API | Any language with curl/fetch. Authed by API key. | `POST https://api.robotresources.ai/v1/route` |
|
|
55
|
+
| Cursor / Claude Code MCP | Scraper MCP wired into your tool's config (web fetches → 91% smaller markdown). | `~/.cursor/mcp.json` or `~/.claude/settings.json` |
|
|
58
56
|
|
|
59
|
-
|
|
60
|
-
npx -y @robot-resources/scraper-mcp # Scraper compression
|
|
61
|
-
```
|
|
57
|
+
Full integration docs: https://robotresources.ai/docs
|
|
62
58
|
|
|
63
|
-
##
|
|
59
|
+
## Architecture (post-PR-2.5)
|
|
64
60
|
|
|
65
|
-
|
|
61
|
+
The router used to be a Python daemon on `localhost:3838`. **Not anymore.** It now runs in-process inside whichever surface consumes it:
|
|
66
62
|
|
|
67
|
-
|
|
63
|
+
- **OpenClaw** — the plugin's `register()` starts an HTTP server on `127.0.0.1:18790` inside OC's node process. Lifetime tied to OC. Zero daemon to keep alive.
|
|
64
|
+
- **JS agents** — call `routePrompt()` directly. No HTTP at all. Pure function.
|
|
65
|
+
- **Python / curl** — call `POST /v1/route` on `api.robotresources.ai`. Server-side classifier on Cloudflare Workers.
|
|
68
66
|
|
|
69
|
-
|
|
67
|
+
User provider keys never leave the user's machine. The platform never receives, stores, or transmits them.
|
|
70
68
|
|
|
71
69
|
## Telemetry
|
|
72
70
|
|
|
73
|
-
Anonymous
|
|
71
|
+
Anonymous, fire-and-forget, opt-in via the wizard's API-key provisioning. Events: `wizard_started`, `wizard_path_chosen`, `install_complete`, `route_completed`, `route_via_api`, `route_via_lib`. No personal data, no request content, no provider keys.
|
|
74
72
|
|
|
75
|
-
|
|
73
|
+
## Pricing
|
|
74
|
+
|
|
75
|
+
Free. Unlimited. Your API keys never leave your machine.
|
|
76
76
|
|
|
77
77
|
## Links
|
|
78
78
|
|
|
79
79
|
- Website: https://robotresources.ai
|
|
80
|
+
- Docs: https://robotresources.ai/docs
|
|
80
81
|
- Dashboard: https://robotresources.ai/dashboard
|
|
81
|
-
-
|
|
82
|
+
- HTTP API: `POST https://api.robotresources.ai/v1/route`
|
|
82
83
|
- npm: https://www.npmjs.com/package/robot-resources
|
|
83
84
|
- GitHub: https://github.com/robot-resources/packages
|
|
84
85
|
- Discord: https://robotresources.ai/discord
|
|
85
86
|
- Contact: agent@robotresources.ai
|
|
87
|
+
- Agent docs: https://robotresources.ai/llms.txt
|
|
86
88
|
|
|
87
89
|
## License
|
|
88
90
|
|
package/bin/setup.js
CHANGED
|
@@ -1,20 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { runWizard } from '../lib/wizard.js';
|
|
3
|
+
import { runWizard, runUninstallCommand } from '../lib/wizard.js';
|
|
4
4
|
|
|
5
5
|
const args = process.argv.slice(2);
|
|
6
|
-
const explicitNonInteractive =
|
|
7
|
-
args.includes('--non-interactive') || args.includes('--yes') || args.includes('-y');
|
|
8
|
-
const targetArg = args.find((a) => a.startsWith('--for='));
|
|
9
|
-
const target = targetArg ? targetArg.slice('--for='.length) : null;
|
|
10
6
|
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
const
|
|
7
|
+
// --uninstall ships in Phase 0 as the counterpart to install. --purge also
|
|
8
|
+
// wipes ~/.robot-resources/config.json (api_key + claim_url); without it,
|
|
9
|
+
// the api_key is preserved across a reinstall.
|
|
10
|
+
if (args.includes('--uninstall')) {
|
|
11
|
+
const purge = args.includes('--purge');
|
|
12
|
+
runUninstallCommand({ purge }).catch((err) => {
|
|
13
|
+
console.error(`\n ✗ Uninstall failed: ${err.message}\n`);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
});
|
|
16
|
+
} else {
|
|
17
|
+
const explicitNonInteractive =
|
|
18
|
+
args.includes('--non-interactive') || args.includes('--yes') || args.includes('-y');
|
|
19
|
+
const targetArg = args.find((a) => a.startsWith('--for='));
|
|
20
|
+
const target = targetArg ? targetArg.slice('--for='.length) : null;
|
|
16
21
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
// Treat piped/CI runs (no TTY on stdin OR stdout) as non-interactive so the
|
|
23
|
+
// wizard never blocks on a prompt that can't be answered. The interactive
|
|
24
|
+
// menu is only opened when both stdin and stdout are real terminals.
|
|
25
|
+
const hasTty = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
26
|
+
const nonInteractive = explicitNonInteractive || !hasTty;
|
|
27
|
+
|
|
28
|
+
runWizard({ nonInteractive, target }).catch((err) => {
|
|
29
|
+
console.error(`\n ✗ Setup failed: ${err.message}\n`);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
});
|
|
32
|
+
}
|
package/lib/non-oc-wizard.js
CHANGED
|
@@ -108,14 +108,17 @@ function showPythonPath() {
|
|
|
108
108
|
success('Python integration');
|
|
109
109
|
blank();
|
|
110
110
|
info('Install:');
|
|
111
|
-
info(' pip install robot-resources
|
|
111
|
+
info(' pip install robot-resources');
|
|
112
112
|
blank();
|
|
113
113
|
info('Use:');
|
|
114
|
-
info(' from
|
|
114
|
+
info(' from robot_resources.router import route');
|
|
115
115
|
info(' decision = route(\'write a python function\')');
|
|
116
116
|
info(' print(decision[\'selected_model\']) # e.g. \'claude-haiku-4-5\'');
|
|
117
117
|
blank();
|
|
118
|
-
info('
|
|
118
|
+
info('Prefer no SDK? POST directly to https://api.robotresources.ai/v1/route');
|
|
119
|
+
info('with httpx / requests / any HTTP client. See docs.');
|
|
120
|
+
blank();
|
|
121
|
+
info('Full docs: https://robotresources.ai/docs/crewai');
|
|
119
122
|
blank();
|
|
120
123
|
}
|
|
121
124
|
|
|
@@ -156,7 +159,7 @@ function showDocsPath() {
|
|
|
156
159
|
blank();
|
|
157
160
|
info('Integration guides: https://robotresources.ai/docs');
|
|
158
161
|
info('HTTP API: https://robotresources.ai/docs/http-api');
|
|
159
|
-
info('GitHub: https://github.com/robot-resources/
|
|
162
|
+
info('GitHub: https://github.com/robot-resources/packages');
|
|
160
163
|
blank();
|
|
161
164
|
}
|
|
162
165
|
|
|
@@ -203,6 +206,7 @@ export async function runNonOcWizard({ nonInteractive = false, target = null } =
|
|
|
203
206
|
info(' npx robot-resources --for=claude-code # Claude Code MCP config');
|
|
204
207
|
info(' npx robot-resources --for=docs # docs URL');
|
|
205
208
|
blank();
|
|
209
|
+
await emitPathChosen('noninteractive_no_target');
|
|
206
210
|
return;
|
|
207
211
|
}
|
|
208
212
|
|
|
@@ -228,8 +232,13 @@ export async function runNonOcWizard({ nonInteractive = false, target = null } =
|
|
|
228
232
|
],
|
|
229
233
|
});
|
|
230
234
|
} catch (err) {
|
|
231
|
-
// User hit Ctrl-C or terminal closed — exit cleanly
|
|
232
|
-
|
|
235
|
+
// User hit Ctrl-C or terminal closed — exit cleanly, but mark the funnel
|
|
236
|
+
// so we can distinguish "agent shown the prompt and bailed" from
|
|
237
|
+
// "wizard never reached the prompt at all" in Supabase.
|
|
238
|
+
if (err && (err.name === 'ExitPromptError' || err.code === 'ABORT_ERR')) {
|
|
239
|
+
await emitPathChosen('aborted');
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
233
242
|
throw err;
|
|
234
243
|
}
|
|
235
244
|
|
package/lib/tool-config.js
CHANGED
|
@@ -301,7 +301,7 @@ function configureOpenClaw() {
|
|
|
301
301
|
);
|
|
302
302
|
}
|
|
303
303
|
|
|
304
|
-
instructions.push('Docs: https://github.com/robot-resources/
|
|
304
|
+
instructions.push('Docs: https://github.com/robot-resources/packages');
|
|
305
305
|
|
|
306
306
|
return {
|
|
307
307
|
name: 'OpenClaw',
|
|
@@ -339,7 +339,7 @@ function printManualInstructions() {
|
|
|
339
339
|
' # OpenAI(base_url="http://localhost:3838/v1")',
|
|
340
340
|
' # model = "gemini-2.5-flash"',
|
|
341
341
|
'',
|
|
342
|
-
'Docs: https://github.com/robot-resources/
|
|
342
|
+
'Docs: https://github.com/robot-resources/packages',
|
|
343
343
|
],
|
|
344
344
|
};
|
|
345
345
|
}
|
package/lib/uninstall.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, rmSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { stripJson5 } from './json5.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Single source of truth for `npx robot-resources --uninstall`.
|
|
8
|
+
*
|
|
9
|
+
* Reverses the install actions in tool-config.js: removes the router and
|
|
10
|
+
* scraper OC plugin directories, deletes their entries from openclaw.json
|
|
11
|
+
* (plugins.entries + plugins.allow + mcp.servers).
|
|
12
|
+
*
|
|
13
|
+
* Phase 0 scope is OC-only. Phase 3 will extend this with shell-config
|
|
14
|
+
* removal (NODE_OPTIONS line) and `pip uninstall robot-resources` for the
|
|
15
|
+
* Node and Python shim install paths.
|
|
16
|
+
*
|
|
17
|
+
* `~/.robot-resources/config.json` is preserved by default so a subsequent
|
|
18
|
+
* re-install reuses the same api_key (and the user's claim_url stays valid).
|
|
19
|
+
* Pass { purge: true } to wipe it as well.
|
|
20
|
+
*
|
|
21
|
+
* Returns { components_removed: string[], errors: { component, message }[] }
|
|
22
|
+
* for telemetry. Failure to remove one component never aborts the others —
|
|
23
|
+
* a partial uninstall is still progress, and we want to record what worked.
|
|
24
|
+
*/
|
|
25
|
+
export function runUninstall({ purge = false } = {}) {
|
|
26
|
+
const components_removed = [];
|
|
27
|
+
const errors = [];
|
|
28
|
+
|
|
29
|
+
// 1. Plugin directories under ~/.openclaw/extensions/
|
|
30
|
+
const pluginDirs = [
|
|
31
|
+
{ id: 'robot-resources-router', label: 'router_plugin_dir' },
|
|
32
|
+
{ id: 'robot-resources-scraper-oc-plugin', label: 'scraper_plugin_dir' },
|
|
33
|
+
];
|
|
34
|
+
for (const { id, label } of pluginDirs) {
|
|
35
|
+
const path = join(homedir(), '.openclaw', 'extensions', id);
|
|
36
|
+
if (!existsSync(path)) continue;
|
|
37
|
+
try {
|
|
38
|
+
rmSync(path, { recursive: true, force: true });
|
|
39
|
+
components_removed.push(label);
|
|
40
|
+
} catch (err) {
|
|
41
|
+
errors.push({ component: label, message: err.message });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 2. openclaw.json — strip our entries from plugins.entries, plugins.allow,
|
|
46
|
+
// and mcp.servers. Leave everything else (other plugins, user config) alone.
|
|
47
|
+
// Idempotent: if openclaw.json is missing or malformed, skip silently —
|
|
48
|
+
// that's the right behavior for "cleanup what you can find."
|
|
49
|
+
const ocConfigPath = join(homedir(), '.openclaw', 'openclaw.json');
|
|
50
|
+
if (existsSync(ocConfigPath)) {
|
|
51
|
+
try {
|
|
52
|
+
const config = JSON.parse(stripJson5(readFileSync(ocConfigPath, 'utf-8')));
|
|
53
|
+
let mutated = false;
|
|
54
|
+
|
|
55
|
+
if (config?.plugins?.entries) {
|
|
56
|
+
for (const id of ['robot-resources-router', 'robot-resources-scraper-oc-plugin']) {
|
|
57
|
+
if (config.plugins.entries[id]) {
|
|
58
|
+
delete config.plugins.entries[id];
|
|
59
|
+
mutated = true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (Array.isArray(config?.plugins?.allow)) {
|
|
65
|
+
const before = config.plugins.allow.length;
|
|
66
|
+
config.plugins.allow = config.plugins.allow.filter(
|
|
67
|
+
(id) => id !== 'robot-resources-router' && id !== 'robot-resources-scraper-oc-plugin',
|
|
68
|
+
);
|
|
69
|
+
if (config.plugins.allow.length !== before) mutated = true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (config?.mcp?.servers?.['robot-resources-scraper']) {
|
|
73
|
+
delete config.mcp.servers['robot-resources-scraper'];
|
|
74
|
+
mutated = true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (mutated) {
|
|
78
|
+
writeFileSync(ocConfigPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
79
|
+
components_removed.push('openclaw_config_entries');
|
|
80
|
+
}
|
|
81
|
+
} catch (err) {
|
|
82
|
+
errors.push({ component: 'openclaw_config_entries', message: err.message });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 3. Optionally wipe ~/.robot-resources/config.json (and any siblings)
|
|
87
|
+
if (purge) {
|
|
88
|
+
const rrDir = join(homedir(), '.robot-resources');
|
|
89
|
+
if (existsSync(rrDir)) {
|
|
90
|
+
try {
|
|
91
|
+
rmSync(rrDir, { recursive: true, force: true });
|
|
92
|
+
components_removed.push('rr_config_dir');
|
|
93
|
+
} catch (err) {
|
|
94
|
+
errors.push({ component: 'rr_config_dir', message: err.message });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { components_removed, errors };
|
|
100
|
+
}
|
package/lib/wizard.js
CHANGED
|
@@ -8,6 +8,7 @@ import { configureToolRouting, registerScraperMcp, restartOpenClawGateway } from
|
|
|
8
8
|
import { checkHealth } from './health-report.js';
|
|
9
9
|
import { header, step, success, warn, error, info, blank, summary } from './ui.js';
|
|
10
10
|
import { runNonOcWizard } from './non-oc-wizard.js';
|
|
11
|
+
import { runUninstall } from './uninstall.js';
|
|
11
12
|
|
|
12
13
|
// Stamped onto every CLI telemetry payload so we can tell which `robot-resources`
|
|
13
14
|
// version a user actually ran. Without this, npx-cached old installers look
|
|
@@ -45,24 +46,17 @@ const CLI_VERSION = (() => {
|
|
|
45
46
|
export async function runWizard({ nonInteractive = false, target = null } = {}) {
|
|
46
47
|
header();
|
|
47
48
|
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
// get the print-and-exit hint with the supported --for= options.
|
|
53
|
-
// Pre-PR-8 this was a 17-line print-and-exit; PR 8 made it interactive.
|
|
54
|
-
if (!isOpenClawInstalled()) {
|
|
55
|
-
await runNonOcWizard({ nonInteractive, target });
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
|
|
49
|
+
// Detect OC once up front. Used both to branch into the non-OC wizard and
|
|
50
|
+
// to tag the wizard_started payload, so the funnel can be segmented OC vs
|
|
51
|
+
// non-OC without a second event type.
|
|
52
|
+
const openclawDetected = isOpenClawInstalled();
|
|
59
53
|
const wizardStartMs = Date.now();
|
|
60
54
|
|
|
61
55
|
const results = {
|
|
62
56
|
auth: false,
|
|
63
57
|
authMethod: null, // 'config' | 'apikey' | 'auto'
|
|
64
58
|
pluginInstalled: false,
|
|
65
|
-
openclawDetected
|
|
59
|
+
openclawDetected,
|
|
66
60
|
openclawConfigPatched: false,
|
|
67
61
|
scraperMcpRegistered: false,
|
|
68
62
|
scraper: false,
|
|
@@ -70,9 +64,11 @@ export async function runWizard({ nonInteractive = false, target = null } = {})
|
|
|
70
64
|
|
|
71
65
|
// ── Step 0: Provision API key (before anything else) ────────────────────
|
|
72
66
|
//
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
//
|
|
67
|
+
// Runs for BOTH the OC and non-OC paths. Provisioning before the non-OC
|
|
68
|
+
// hand-off closes the funnel blind spot where every non-OpenClaw install
|
|
69
|
+
// was invisible to telemetry (no api_keys row, no wizard_started, no
|
|
70
|
+
// agent_signup_meta). If the session dies later, telemetry still works
|
|
71
|
+
// for all tools. Single fetch() with 10s timeout — no prompts, no browser.
|
|
76
72
|
|
|
77
73
|
{
|
|
78
74
|
const config = readConfig();
|
|
@@ -116,10 +112,11 @@ export async function runWizard({ nonInteractive = false, target = null } = {})
|
|
|
116
112
|
|
|
117
113
|
// ── Funnel marker: wizard_started ───────────────────────────────────────
|
|
118
114
|
//
|
|
119
|
-
// Sent immediately after auth
|
|
120
|
-
// point even if
|
|
121
|
-
//
|
|
122
|
-
//
|
|
115
|
+
// Sent immediately after auth, before either path branches, so we have
|
|
116
|
+
// proof the wizard reached this point even if a later step crashes. Pairs
|
|
117
|
+
// with install_complete (OC path) or wizard_path_chosen (non-OC path) to
|
|
118
|
+
// give us a "started → done" funnel. The openclaw_detected field lets us
|
|
119
|
+
// segment OC vs non-OC funnels without a second event type.
|
|
123
120
|
//
|
|
124
121
|
// Timeout asymmetry vs install_complete (5s, no retry vs 10s × 2 attempts):
|
|
125
122
|
// wizard_started is a best-effort funnel marker — losing it just means we
|
|
@@ -144,6 +141,7 @@ export async function runWizard({ nonInteractive = false, target = null } = {})
|
|
|
144
141
|
cli_version: CLI_VERSION,
|
|
145
142
|
auth_method: results.authMethod,
|
|
146
143
|
non_interactive: nonInteractive,
|
|
144
|
+
openclaw_detected: openclawDetected,
|
|
147
145
|
},
|
|
148
146
|
}),
|
|
149
147
|
signal: AbortSignal.timeout(5_000),
|
|
@@ -153,6 +151,15 @@ export async function runWizard({ nonInteractive = false, target = null } = {})
|
|
|
153
151
|
}
|
|
154
152
|
}
|
|
155
153
|
|
|
154
|
+
// Non-OC branch. Hands off to the multi-agent compatibility wizard which
|
|
155
|
+
// routes the user to the right install path (npm install / pip install /
|
|
156
|
+
// MCP config / docs / install-OC). The non-OC wizard's wizard_path_chosen
|
|
157
|
+
// telemetry now fires too, since Step 0 above provisioned an api_key.
|
|
158
|
+
if (!openclawDetected) {
|
|
159
|
+
await runNonOcWizard({ nonInteractive, target });
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
156
163
|
// ── Step 1: Tool Routing Configuration ──────────────────────────────────
|
|
157
164
|
//
|
|
158
165
|
// Installs the OC plugin (which is @robot-resources/router — the router
|
|
@@ -166,7 +173,6 @@ export async function runWizard({ nonInteractive = false, target = null } = {})
|
|
|
166
173
|
const toolResults = configureToolRouting();
|
|
167
174
|
results.tools = toolResults;
|
|
168
175
|
|
|
169
|
-
results.openclawDetected = isOpenClawInstalled();
|
|
170
176
|
const ocResult = toolResults.find((r) => r.name === 'OpenClaw');
|
|
171
177
|
if (ocResult) {
|
|
172
178
|
results.pluginInstalled =
|
|
@@ -384,7 +390,7 @@ export async function runWizard({ nonInteractive = false, target = null } = {})
|
|
|
384
390
|
// Telegram survives this restart. If the session dies here, the agent
|
|
385
391
|
// picks up on the next message with all tools loaded.
|
|
386
392
|
|
|
387
|
-
if (
|
|
393
|
+
if (openclawDetected && (results.tools?.some(r => r.action === 'installed') || scraperRegistered)) {
|
|
388
394
|
try {
|
|
389
395
|
await restartOpenClawGateway();
|
|
390
396
|
success('OpenClaw gateway restarted');
|
|
@@ -393,3 +399,71 @@ export async function runWizard({ nonInteractive = false, target = null } = {})
|
|
|
393
399
|
}
|
|
394
400
|
}
|
|
395
401
|
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Uninstall counterpart to runWizard. Removes the OC plugin install side
|
|
405
|
+
* (router + scraper plugin dirs, openclaw.json entries) via uninstall.js.
|
|
406
|
+
*
|
|
407
|
+
* config.json (and its api_key) is preserved by default so a later re-install
|
|
408
|
+
* keeps the same identity. `--purge` wipes ~/.robot-resources/ as well.
|
|
409
|
+
*
|
|
410
|
+
* Telemetry: emits `wizard_uninstalled` with the list of components actually
|
|
411
|
+
* removed plus any per-component errors. Fire-and-forget — never block the
|
|
412
|
+
* uninstall on telemetry latency or failure.
|
|
413
|
+
*/
|
|
414
|
+
export async function runUninstallCommand({ purge = false } = {}) {
|
|
415
|
+
header();
|
|
416
|
+
step(purge ? 'Uninstalling Robot Resources (purge)...' : 'Uninstalling Robot Resources...');
|
|
417
|
+
|
|
418
|
+
const result = runUninstall({ purge });
|
|
419
|
+
|
|
420
|
+
blank();
|
|
421
|
+
if (result.components_removed.length === 0 && result.errors.length === 0) {
|
|
422
|
+
info('Nothing to remove — Robot Resources was not installed in this account.');
|
|
423
|
+
} else {
|
|
424
|
+
if (result.components_removed.length > 0) {
|
|
425
|
+
success(`Removed: ${result.components_removed.join(', ')}`);
|
|
426
|
+
}
|
|
427
|
+
for (const e of result.errors) {
|
|
428
|
+
warn(`${e.component}: ${e.message}`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Preserve the api_key (unless --purge) so re-running `npx robot-resources`
|
|
433
|
+
// doesn't issue a second key. Tell the user explicitly so they can purge if
|
|
434
|
+
// they really want a clean slate.
|
|
435
|
+
if (!purge) {
|
|
436
|
+
blank();
|
|
437
|
+
info('Kept ~/.robot-resources/config.json (your api_key + claim_url).');
|
|
438
|
+
info('Re-run with --purge to wipe it.');
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Best-effort telemetry — same shape as the rest of the CLI's calls.
|
|
442
|
+
try {
|
|
443
|
+
const config = readConfig();
|
|
444
|
+
if (config.api_key) {
|
|
445
|
+
const platformUrl = process.env.RR_PLATFORM_URL || 'https://api.robotresources.ai';
|
|
446
|
+
await fetch(`${platformUrl}/v1/telemetry`, {
|
|
447
|
+
method: 'POST',
|
|
448
|
+
headers: {
|
|
449
|
+
'Authorization': `Bearer ${config.api_key}`,
|
|
450
|
+
'Content-Type': 'application/json',
|
|
451
|
+
},
|
|
452
|
+
body: JSON.stringify({
|
|
453
|
+
product: 'cli',
|
|
454
|
+
event_type: 'wizard_uninstalled',
|
|
455
|
+
payload: {
|
|
456
|
+
cli_version: CLI_VERSION,
|
|
457
|
+
purge,
|
|
458
|
+
components_removed: result.components_removed,
|
|
459
|
+
error_count: result.errors.length,
|
|
460
|
+
platform: process.platform,
|
|
461
|
+
},
|
|
462
|
+
}),
|
|
463
|
+
signal: AbortSignal.timeout(5_000),
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
} catch {
|
|
467
|
+
// Non-fatal — telemetry must never block the uninstall path.
|
|
468
|
+
}
|
|
469
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "robot-resources",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.2",
|
|
4
4
|
"description": "Robot Resources — AI agent tools. One command to install everything.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"homepage": "https://robotresources.ai",
|
|
48
48
|
"bugs": {
|
|
49
|
-
"url": "https://github.com/robot-resources/
|
|
49
|
+
"url": "https://github.com/robot-resources/packages/issues"
|
|
50
50
|
},
|
|
51
51
|
"publishConfig": {
|
|
52
52
|
"access": "public"
|