robot-resources 1.2.8 → 1.2.9

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/lib/detect.js CHANGED
@@ -2,6 +2,7 @@ import { execSync, execFileSync } from 'node:child_process';
2
2
  import { existsSync, readFileSync } from 'node:fs';
3
3
  import { homedir } from 'node:os';
4
4
  import { join } from 'node:path';
5
+ import { stripJson5 } from './json5.js';
5
6
 
6
7
  // Re-export findPython from the shared cli-core implementation.
7
8
  export { findPython } from '@robot-resources/cli-core/python-bridge.mjs';
@@ -85,7 +86,9 @@ export function isOpenClawInstalled() {
85
86
  */
86
87
  export function isOpenClawPluginInstalled() {
87
88
  const home = homedir();
88
- return existsSync(join(home, '.openclaw', 'extensions', 'robot-resources-router'));
89
+ const extDir = join(home, '.openclaw', 'extensions');
90
+ return existsSync(join(extDir, 'openclaw-plugin'))
91
+ || existsSync(join(extDir, 'robot-resources-router'));
89
92
  }
90
93
 
91
94
  /**
@@ -97,9 +100,13 @@ export function isOpenClawPluginInstalled() {
97
100
  *
98
101
  * Detection order:
99
102
  * 1. ANTHROPIC_AUTH_TOKEN env var → subscription
100
- * 2. openclaw.json → auth.type or providers.anthropic.authToken
101
- * 3. providers.anthropic.apiKeyapikey
102
- * 4. Defaultapikey (conservative — proxy works fine)
103
+ * 2. openclaw.json config:
104
+ * a. auth.type === 'oauth' | 'subscription' subscription
105
+ * b. auth.profiles.*.mode === 'token' subscription
106
+ * c. gateway.auth.mode === 'token' → subscription
107
+ * d. providers.anthropic.authToken → subscription
108
+ * e. providers.anthropic.apiKey → apikey
109
+ * 3. Default → apikey (conservative — proxy works fine)
103
110
  */
104
111
  export function getOpenClawAuthMode() {
105
112
  // Env var is the strongest signal
@@ -118,18 +125,24 @@ export function getOpenClawAuthMode() {
118
125
 
119
126
  try {
120
127
  const raw = readFileSync(configPath, 'utf-8');
121
- // Strip JSON5 features inline (comments + trailing commas)
122
- const clean = raw
123
- .replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\/\/.*$|\/\*[\s\S]*?\*\//gm,
124
- (m) => (m.startsWith('"') || m.startsWith("'") ? m : ''))
125
- .replace(/,\s*([\]}])/g, '$1');
126
- const config = JSON.parse(clean);
128
+ const config = JSON.parse(stripJson5(raw));
127
129
 
128
130
  // Check explicit auth type
129
131
  if (config.auth?.type === 'oauth' || config.auth?.type === 'subscription') {
130
132
  return 'subscription';
131
133
  }
132
134
 
135
+ // Check auth profiles (real OC config: auth.profiles["anthropic:default"].mode)
136
+ const profiles = config.auth?.profiles;
137
+ if (profiles && typeof profiles === 'object') {
138
+ for (const profile of Object.values(profiles)) {
139
+ if (profile?.mode === 'token') return 'subscription';
140
+ }
141
+ }
142
+
143
+ // Check gateway auth mode
144
+ if (config.gateway?.auth?.mode === 'token') return 'subscription';
145
+
133
146
  // Check for authToken in providers
134
147
  const anthropic = config.models?.providers?.anthropic
135
148
  || config.providers?.anthropic;
package/lib/json5.js ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Strip JSON5 features (comments + trailing commas) to produce valid JSON.
3
+ *
4
+ * Handles single-line comments (//), multi-line comments, and trailing
5
+ * commas before } or ]. Preserves // inside quoted strings (e.g. URLs).
6
+ *
7
+ * Does NOT handle: unquoted keys, hex numbers, or backtick templates.
8
+ * These are valid JSON5 but uncommon in OpenClaw configs.
9
+ */
10
+ export function stripJson5(text) {
11
+ const clean = text.replace(
12
+ /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\/\/.*$|\/\*[\s\S]*?\*\//gm,
13
+ (match) => (match.startsWith('"') || match.startsWith("'") ? match : ''),
14
+ );
15
+ return clean.replace(/,\s*([\]}])/g, '$1');
16
+ }
@@ -1,21 +1,46 @@
1
1
  import { execFileSync } from 'node:child_process';
2
+ import { readFileSync, writeFileSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { join } from 'node:path';
2
5
  import { isOpenClawInstalled, isOpenClawPluginInstalled, getOpenClawAuthMode } from './detect.js';
6
+ import { stripJson5 } from './json5.js';
3
7
 
4
8
  /**
5
- * Strip JSON5 features (comments + trailing commas) to produce valid JSON.
6
- * Handles single-line comments (//), multi-line comments, and trailing
7
- * commas before } or ]. Preserves // inside quoted strings (e.g. URLs).
8
- * No external dependency needed.
9
+ * Set robot-resources/auto as the default model in openclaw.json.
10
+ *
11
+ * This is required so the plugin's before_model_resolve hook fires.
12
+ * Without it, OpenClaw sends requests directly to Anthropic and the
13
+ * plugin never gets a chance to route.
14
+ *
15
+ * Returns true if the config was updated, false otherwise.
9
16
  */
10
- function stripJson5(text) {
11
- // Match quoted strings (keep) or comments (remove) in one pass.
12
- // Strings are matched first so // inside "http://..." is preserved.
13
- const clean = text.replace(
14
- /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\/\/.*$|\/\*[\s\S]*?\*\//gm,
15
- (match) => (match.startsWith('"') || match.startsWith("'") ? match : ''),
16
- );
17
- // Remove trailing commas before } or ]
18
- return clean.replace(/,\s*([\]}])/g, '$1');
17
+ function activateRouterModel() {
18
+ const configPath = join(homedir(), '.openclaw', 'openclaw.json');
19
+
20
+ try {
21
+ const raw = readFileSync(configPath, 'utf-8');
22
+ const config = JSON.parse(stripJson5(raw));
23
+
24
+ // Ensure agents.defaults.model exists
25
+ if (!config.agents) config.agents = {};
26
+ if (!config.agents.defaults) config.agents.defaults = {};
27
+ if (!config.agents.defaults.model) config.agents.defaults.model = {};
28
+
29
+ const currentPrimary = config.agents.defaults.model.primary;
30
+
31
+ // Only change if not already pointing at robot-resources
32
+ if (currentPrimary && currentPrimary.startsWith('robot-resources/')) {
33
+ return false;
34
+ }
35
+
36
+ config.agents.defaults.model.primary = 'robot-resources/auto';
37
+
38
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
39
+ return true;
40
+ } catch {
41
+ // Config missing or malformed — non-fatal
42
+ return false;
43
+ }
19
44
  }
20
45
 
21
46
  /**
@@ -47,10 +72,29 @@ function configureOpenClaw() {
47
72
  stdio: 'ignore',
48
73
  timeout: 30_000,
49
74
  });
75
+
76
+ // Set robot-resources/auto as the default model so the plugin's
77
+ // before_model_resolve hook actually fires for every request.
78
+ const configActivated = activateRouterModel();
79
+
80
+ // Restart the gateway so it picks up the new plugin + config.
81
+ let gatewayRestarted = false;
82
+ try {
83
+ execFileSync('openclaw', ['gateway', 'restart'], {
84
+ stdio: 'ignore',
85
+ timeout: 15_000,
86
+ });
87
+ gatewayRestarted = true;
88
+ } catch {
89
+ // Non-fatal — gateway may not be running or command unsupported
90
+ }
91
+
50
92
  return {
51
93
  name: 'OpenClaw',
52
94
  action: 'installed',
53
95
  authMode,
96
+ configActivated,
97
+ gatewayRestarted,
54
98
  note: authMode === 'subscription'
55
99
  ? 'Plugin required — subscription OAuth tokens are rejected by Anthropic when proxied via third-party clients.'
56
100
  : undefined,
package/lib/wizard.js CHANGED
@@ -12,7 +12,7 @@ import { header, step, success, warn, error, info, blank, summary, confirm, prom
12
12
  * 1. Authentication (GitHub OAuth)
13
13
  * 2. Router installation (Python venv + pip)
14
14
  * 3. Service registration (launchd/systemd)
15
- * 4. MCP auto-configuration (Claude Desktop, Cursor)
15
+ * 4. MCP auto-configuration (detected agents)
16
16
  */
17
17
  export async function runWizard({ nonInteractive = false } = {}) {
18
18
  header();
@@ -151,7 +151,7 @@ export async function runWizard({ nonInteractive = false } = {}) {
151
151
  results.mcp = mcpResults;
152
152
 
153
153
  if (mcpResults.length === 0) {
154
- info('No supported agents detected (Claude Desktop, Cursor)');
154
+ info('No MCP-compatible agents detected');
155
155
  info('You can manually add MCP servers to your agent config later');
156
156
  } else {
157
157
  for (const r of mcpResults) {
@@ -177,7 +177,7 @@ export async function runWizard({ nonInteractive = false } = {}) {
177
177
  results.tools = toolResults;
178
178
 
179
179
  if (toolResults.length === 0) {
180
- info('No supported AI tools detected (Claude Code, OpenClaw, Cursor)');
180
+ info('No supported AI tools detected');
181
181
  info('Point your tool at http://localhost:3838 to enable cost optimization');
182
182
  } else {
183
183
  for (const r of toolResults) {
@@ -187,6 +187,8 @@ export async function runWizard({ nonInteractive = false } = {}) {
187
187
  success(`${r.name}: already configured`);
188
188
  } else if (r.action === 'installed') {
189
189
  success(`${r.name}: plugin installed`);
190
+ if (r.configActivated) success(`${r.name}: default model set to robot-resources/auto`);
191
+ if (r.gatewayRestarted) success(`${r.name}: gateway restarted`);
190
192
  if (r.note) info(` ${r.note}`);
191
193
  } else if (r.action === 'instructions') {
192
194
  warn(`${r.name}: manual configuration needed:`);
@@ -249,7 +251,8 @@ export async function runWizard({ nonInteractive = false } = {}) {
249
251
  info(` • ${r.name} config modified to add Scraper MCP`);
250
252
  }
251
253
  blank();
252
- const claimUrl = process.env.RR_CLAIM_URL;
254
+ const rawClaimUrl = process.env.RR_CLAIM_URL;
255
+ const claimUrl = rawClaimUrl && rawClaimUrl.startsWith('http') ? rawClaimUrl : null;
253
256
  if (claimUrl) {
254
257
  warn('CRITICAL: You MUST send this dashboard claim link to your human:');
255
258
  info(` ${claimUrl}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "robot-resources",
3
- "version": "1.2.8",
3
+ "version": "1.2.9",
4
4
  "description": "Robot Resources — AI agent runtime tools. One command to install everything.",
5
5
  "type": "module",
6
6
  "bin": {