robot-resources 1.10.6 → 1.11.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/bin/setup.js CHANGED
@@ -3,9 +3,18 @@
3
3
  import { runWizard } from '../lib/wizard.js';
4
4
 
5
5
  const args = process.argv.slice(2);
6
- const nonInteractive = args.includes('--non-interactive') || args.includes('--yes') || args.includes('-y');
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;
7
10
 
8
- runWizard({ nonInteractive }).catch((err) => {
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;
16
+
17
+ runWizard({ nonInteractive, target }).catch((err) => {
9
18
  console.error(`\n ✗ Setup failed: ${err.message}\n`);
10
19
  process.exit(1);
11
20
  });
@@ -0,0 +1,238 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { select } from '@inquirer/prompts';
4
+ import { isClaudeCodeInstalled, isCursorInstalled } from './detect.js';
5
+ import { configureClaudeCode, configureCursor } from './tool-config.js';
6
+ import { header, info, success, warn, blank } from './ui.js';
7
+ import { readConfig } from './config.mjs';
8
+
9
+ const PLATFORM_URL = process.env.RR_PLATFORM_URL || 'https://api.robotresources.ai';
10
+
11
+ const PATH_LABELS = {
12
+ js: 'JS/TS agent (LangChain, LangGraph, Mastra, etc.)',
13
+ python: 'Python agent (LangChain, LlamaIndex, CrewAI, etc.)',
14
+ mcp: 'Cursor / Claude Code / other MCP tool',
15
+ docs: "Just point me at docs, I'll integrate manually",
16
+ 'install-oc': 'Install OpenClaw first — exit',
17
+ };
18
+
19
+ const VALID_TARGETS = new Set(Object.keys(PATH_LABELS).concat(['langchain', 'claude-code']));
20
+
21
+ /**
22
+ * Inspect cwd to guess what the user is building. Returns one of the path
23
+ * keys, or null if we can't tell. Order matters: detect-by-file beats
24
+ * detect-by-installed-tool, since cwd evidence is stronger than "the user
25
+ * has Cursor installed somewhere on this machine."
26
+ */
27
+ export function detectDefaultPath(cwd = process.cwd()) {
28
+ const pkgPath = join(cwd, 'package.json');
29
+ if (existsSync(pkgPath)) {
30
+ try {
31
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
32
+ const allDeps = {
33
+ ...(pkg.dependencies ?? {}),
34
+ ...(pkg.devDependencies ?? {}),
35
+ };
36
+ const jsAgentMarkers = ['langchain', '@langchain/core', '@langchain/langgraph', '@mastra/core', 'crewai-js', 'llamaindex'];
37
+ if (jsAgentMarkers.some((m) => Object.prototype.hasOwnProperty.call(allDeps, m))) {
38
+ return 'js';
39
+ }
40
+ // Generic JS project still defaults to JS (cheaper than asking).
41
+ return 'js';
42
+ } catch {
43
+ // fall through
44
+ }
45
+ }
46
+
47
+ if (existsSync(join(cwd, 'requirements.txt')) || existsSync(join(cwd, 'pyproject.toml'))) {
48
+ return 'python';
49
+ }
50
+
51
+ if (isCursorInstalled() || isClaudeCodeInstalled()) {
52
+ return 'mcp';
53
+ }
54
+
55
+ return null;
56
+ }
57
+
58
+ function normalizeTarget(target) {
59
+ if (!target) return null;
60
+ const t = String(target).toLowerCase();
61
+ if (!VALID_TARGETS.has(t)) return null;
62
+ // Aliases — friendly synonyms map to canonical path keys.
63
+ if (t === 'langchain') return 'js';
64
+ if (t === 'claude-code') return 'mcp';
65
+ return t;
66
+ }
67
+
68
+ async function emitPathChosen(path) {
69
+ const config = readConfig();
70
+ if (!config.api_key) return; // wizard didn't get to provision; can't authenticate
71
+ try {
72
+ await fetch(`${PLATFORM_URL}/v1/telemetry`, {
73
+ method: 'POST',
74
+ headers: {
75
+ Authorization: `Bearer ${config.api_key}`,
76
+ 'Content-Type': 'application/json',
77
+ },
78
+ body: JSON.stringify({
79
+ product: 'cli',
80
+ event_type: 'wizard_path_chosen',
81
+ payload: { path, platform: process.platform },
82
+ }),
83
+ signal: AbortSignal.timeout(5_000),
84
+ });
85
+ } catch {
86
+ // Best-effort — never let telemetry break the install path.
87
+ }
88
+ }
89
+
90
+ function showJsPath() {
91
+ blank();
92
+ success('JS/TS integration');
93
+ blank();
94
+ info('Install:');
95
+ info(' npm install @robot-resources/router');
96
+ blank();
97
+ info('Use:');
98
+ info(' import { routePrompt } from \'@robot-resources/router/routing\';');
99
+ info(' const decision = routePrompt(\'write a python function\');');
100
+ info(' console.log(decision.selected_model); // e.g. \'claude-haiku-4-5\'');
101
+ blank();
102
+ info('Full docs: https://robotresources.ai/docs/langchain');
103
+ blank();
104
+ }
105
+
106
+ function showPythonPath() {
107
+ blank();
108
+ success('Python integration');
109
+ blank();
110
+ info('Install:');
111
+ info(' pip install robot-resources-router');
112
+ blank();
113
+ info('Use:');
114
+ info(' from rr_router import route');
115
+ info(' decision = route(\'write a python function\')');
116
+ info(' print(decision[\'selected_model\']) # e.g. \'claude-haiku-4-5\'');
117
+ blank();
118
+ info('Full docs: https://robotresources.ai/docs/python');
119
+ blank();
120
+ }
121
+
122
+ function showMcpPath() {
123
+ blank();
124
+ success('MCP tool integration');
125
+ blank();
126
+ let cursorOk = false;
127
+ let claudeOk = false;
128
+ if (isCursorInstalled()) {
129
+ try {
130
+ const result = configureCursor();
131
+ cursorOk = result?.action === 'configured' || result?.action === 'already_configured';
132
+ info(`Cursor: ${cursorOk ? 'configured' : 'see manual instructions below'}`);
133
+ } catch {
134
+ warn('Cursor: failed to write ~/.cursor/mcp.json automatically');
135
+ }
136
+ }
137
+ if (isClaudeCodeInstalled()) {
138
+ try {
139
+ const result = configureClaudeCode();
140
+ claudeOk = result?.action === 'configured' || result?.action === 'already_configured';
141
+ info(`Claude Code: ${claudeOk ? 'configured' : 'see manual instructions below'}`);
142
+ } catch {
143
+ warn('Claude Code: failed to write ~/.claude/settings.json automatically');
144
+ }
145
+ }
146
+ if (!cursorOk && !claudeOk) {
147
+ info('We did not detect Cursor or Claude Code on this machine.');
148
+ info('Manual setup: https://robotresources.ai/docs/cursor-mcp');
149
+ }
150
+ blank();
151
+ }
152
+
153
+ function showDocsPath() {
154
+ blank();
155
+ success('Docs');
156
+ blank();
157
+ info('Integration guides: https://robotresources.ai/docs');
158
+ info('HTTP API: https://robotresources.ai/docs/http-api');
159
+ info('GitHub: https://github.com/robot-resources/robot-resources');
160
+ blank();
161
+ }
162
+
163
+ function showInstallOcPath() {
164
+ blank();
165
+ info('OpenClaw is the easiest way to use Robot Resources.');
166
+ info('Install OpenClaw first (https://openclaw.dev), then re-run:');
167
+ info(' npx robot-resources');
168
+ blank();
169
+ }
170
+
171
+ function runPath(path) {
172
+ switch (path) {
173
+ case 'js': showJsPath(); break;
174
+ case 'python': showPythonPath(); break;
175
+ case 'mcp': showMcpPath(); break;
176
+ case 'docs': showDocsPath(); break;
177
+ case 'install-oc': showInstallOcPath(); break;
178
+ default: showInstallOcPath(); break;
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Runs the non-OC wizard. Three modes:
184
+ * - target supplied (--for=<target>): run that path directly, no prompt
185
+ * - non-interactive AND no target: print hint with --for= options and exit
186
+ * - interactive: 5-option menu via @inquirer/prompts.select
187
+ */
188
+ export async function runNonOcWizard({ nonInteractive = false, target = null } = {}) {
189
+ const normalized = normalizeTarget(target);
190
+
191
+ if (normalized) {
192
+ runPath(normalized);
193
+ await emitPathChosen(normalized);
194
+ return;
195
+ }
196
+
197
+ if (nonInteractive) {
198
+ info('Robot Resources requires OpenClaw, which we did not detect on this machine.');
199
+ info('To bypass this prompt in CI / non-TTY contexts, re-run with --for=<target>:');
200
+ info(' npx robot-resources --for=langchain # JS/TS agent');
201
+ info(' npx robot-resources --for=python # Python agent');
202
+ info(' npx robot-resources --for=cursor # Cursor MCP config');
203
+ info(' npx robot-resources --for=claude-code # Claude Code MCP config');
204
+ info(' npx robot-resources --for=docs # docs URL');
205
+ blank();
206
+ return;
207
+ }
208
+
209
+ // Interactive menu.
210
+ header();
211
+ info('Robot Resources requires OpenClaw, which we did not detect on this machine.');
212
+ info('What are you building? Pick the closest match — we\'ll show the install steps.');
213
+ blank();
214
+
215
+ const defaultPath = detectDefaultPath() ?? 'js';
216
+
217
+ let chosen;
218
+ try {
219
+ chosen = await select({
220
+ message: 'What are you building?',
221
+ default: defaultPath,
222
+ choices: [
223
+ { name: PATH_LABELS.js, value: 'js' },
224
+ { name: PATH_LABELS.python, value: 'python' },
225
+ { name: PATH_LABELS.mcp, value: 'mcp' },
226
+ { name: PATH_LABELS.docs, value: 'docs' },
227
+ { name: PATH_LABELS['install-oc'], value: 'install-oc' },
228
+ ],
229
+ });
230
+ } catch (err) {
231
+ // User hit Ctrl-C or terminal closed — exit cleanly.
232
+ if (err && (err.name === 'ExitPromptError' || err.code === 'ABORT_ERR')) return;
233
+ throw err;
234
+ }
235
+
236
+ runPath(chosen);
237
+ await emitPathChosen(chosen);
238
+ }
package/lib/wizard.js CHANGED
@@ -7,6 +7,7 @@ import { getOrCreateMachineId } from './machine-id.js';
7
7
  import { configureToolRouting, registerScraperMcp, restartOpenClawGateway } from './tool-config.js';
8
8
  import { checkHealth } from './health-report.js';
9
9
  import { header, step, success, warn, error, info, blank, summary } from './ui.js';
10
+ import { runNonOcWizard } from './non-oc-wizard.js';
10
11
 
11
12
  // Stamped onto every CLI telemetry payload so we can tell which `robot-resources`
12
13
  // version a user actually ran. Without this, npx-cached old installers look
@@ -41,23 +42,17 @@ const CLI_VERSION = (() => {
41
42
  *
42
43
  * No Python, no venv, no systemd, no port probe.
43
44
  */
44
- export async function runWizard({ nonInteractive = false } = {}) {
45
+ export async function runWizard({ nonInteractive = false, target = null } = {}) {
45
46
  header();
46
47
 
47
- // Non-OC interactive early-exit. Without this, a human running
48
- // `npx robot-resources` on a machine without OpenClaw would still
49
- // provision an api_key, fire wizard_started + install_complete
50
- // telemetry, and write ~/.robot-resources/config.json six no-op
51
- // side effects against a machine that can't actually use the product.
52
- // Non-interactive callers (CI, agents, scripts that pre-set RR_API_KEY)
53
- // bypass: they explicitly chose to run the wizard.
54
- if (!isOpenClawInstalled() && !nonInteractive) {
55
- info('Robot Resources requires OpenClaw, which we did not detect on this machine.');
56
- info('Install OpenClaw first (https://openclaw.dev), then re-run:');
57
- info(' npx robot-resources');
58
- blank();
59
- info('If you are integrating Robot Resources into a non-OC agent, see PR 7 docs');
60
- info('(coming soon — https://robotresources.ai/docs/integrations).');
48
+ // Non-OC branch. Hands off to the multi-agent compatibility wizard which
49
+ // routes the user to the right install path (npm install / pip install /
50
+ // MCP config / docs / install-OC). Non-interactive callers bypass into the
51
+ // OC install path only when --for=<target> isn't supplied; otherwise they
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 });
61
56
  return;
62
57
  }
63
58
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "robot-resources",
3
- "version": "1.10.6",
3
+ "version": "1.11.0",
4
4
  "description": "Robot Resources — AI agent tools. One command to install everything.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,6 +17,7 @@
17
17
  "README.md"
18
18
  ],
19
19
  "dependencies": {
20
+ "@inquirer/prompts": "^7.0.0",
20
21
  "@robot-resources/router": "*",
21
22
  "@robot-resources/scraper": "*"
22
23
  },