robot-resources 1.11.1 → 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/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
- // Treat piped/CI runs (no TTY on stdin OR stdout) as non-interactive so the
12
- // wizard never blocks on a prompt that can't be answered. The interactive
13
- // menu is only opened when both stdin and stdout are real terminals.
14
- const hasTty = Boolean(process.stdin.isTTY && process.stdout.isTTY);
15
- const nonInteractive = explicitNonInteractive || !hasTty;
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
- runWizard({ nonInteractive, target }).catch((err) => {
18
- console.error(`\n ✗ Setup failed: ${err.message}\n`);
19
- process.exit(1);
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
+ }
@@ -206,6 +206,7 @@ export async function runNonOcWizard({ nonInteractive = false, target = null } =
206
206
  info(' npx robot-resources --for=claude-code # Claude Code MCP config');
207
207
  info(' npx robot-resources --for=docs # docs URL');
208
208
  blank();
209
+ await emitPathChosen('noninteractive_no_target');
209
210
  return;
210
211
  }
211
212
 
@@ -231,8 +232,13 @@ export async function runNonOcWizard({ nonInteractive = false, target = null } =
231
232
  ],
232
233
  });
233
234
  } catch (err) {
234
- // User hit Ctrl-C or terminal closed — exit cleanly.
235
- if (err && (err.name === 'ExitPromptError' || err.code === 'ABORT_ERR')) return;
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
+ }
236
242
  throw err;
237
243
  }
238
244
 
@@ -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
@@ -398,3 +399,71 @@ export async function runWizard({ nonInteractive = false, target = null } = {})
398
399
  }
399
400
  }
400
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.1",
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": {