robot-resources 1.7.1 → 1.7.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/lib/health-report.js +124 -0
- package/lib/machine-id.js +31 -0
- package/lib/tool-config.js +43 -58
- package/lib/wizard.js +52 -47
- package/package.json +2 -2
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { readConfig } from '@robot-resources/cli-core/config.mjs';
|
|
5
|
+
|
|
6
|
+
const PROBE_TIMEOUT_MS = 5_000;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Run post-install health checks against all Robot Resources components.
|
|
10
|
+
*
|
|
11
|
+
* Probes:
|
|
12
|
+
* 1. Router — GET http://127.0.0.1:3838/health
|
|
13
|
+
* 2. Scraper — check openclaw.json for scraper MCP registration
|
|
14
|
+
* 3. Platform — GET {platformUrl}/v1/health with api_key
|
|
15
|
+
* 4. MCP — check openclaw.json for openclaw-plugin registration
|
|
16
|
+
*
|
|
17
|
+
* @returns {{ status: 'healthy'|'partial'|'failed', components: Object, summary: string }}
|
|
18
|
+
*/
|
|
19
|
+
export async function checkHealth() {
|
|
20
|
+
const config = readConfig();
|
|
21
|
+
|
|
22
|
+
const [router, scraper, platform, mcp] = await Promise.all([
|
|
23
|
+
probeRouter(),
|
|
24
|
+
probeScraper(),
|
|
25
|
+
probePlatform(config),
|
|
26
|
+
probeMcp(),
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
const components = { router, scraper, platform, mcp };
|
|
30
|
+
const healthyCount = Object.values(components).filter((c) => c.healthy).length;
|
|
31
|
+
const total = Object.keys(components).length;
|
|
32
|
+
|
|
33
|
+
let status;
|
|
34
|
+
if (healthyCount === total) {
|
|
35
|
+
status = 'healthy';
|
|
36
|
+
} else if (healthyCount === 0) {
|
|
37
|
+
status = 'failed';
|
|
38
|
+
} else {
|
|
39
|
+
status = 'partial';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const failing = Object.entries(components)
|
|
43
|
+
.filter(([, c]) => !c.healthy)
|
|
44
|
+
.map(([name, c]) => `${name}: ${c.detail}`);
|
|
45
|
+
|
|
46
|
+
const summary =
|
|
47
|
+
status === 'healthy'
|
|
48
|
+
? `All ${total} components healthy.`
|
|
49
|
+
: `${healthyCount}/${total} healthy. Issues: ${failing.join('; ')}`;
|
|
50
|
+
|
|
51
|
+
return { status, components, summary };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function probeRouter() {
|
|
55
|
+
try {
|
|
56
|
+
if (typeof fetch === 'undefined') {
|
|
57
|
+
return { healthy: false, detail: 'fetch unavailable' };
|
|
58
|
+
}
|
|
59
|
+
const res = await fetch('http://127.0.0.1:3838/health', {
|
|
60
|
+
signal: AbortSignal.timeout(PROBE_TIMEOUT_MS),
|
|
61
|
+
});
|
|
62
|
+
if (!res.ok) {
|
|
63
|
+
return { healthy: false, detail: `HTTP ${res.status}` };
|
|
64
|
+
}
|
|
65
|
+
const data = await res.json();
|
|
66
|
+
if (data.status === 'healthy' || data.status === 'degraded') {
|
|
67
|
+
return { healthy: true, detail: `running (v${data.version || 'unknown'})` };
|
|
68
|
+
}
|
|
69
|
+
return { healthy: false, detail: `status: ${data.status}` };
|
|
70
|
+
} catch (err) {
|
|
71
|
+
const detail = err.name === 'AbortError' ? 'timeout' : 'unreachable';
|
|
72
|
+
return { healthy: false, detail };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function probeScraper() {
|
|
77
|
+
try {
|
|
78
|
+
const ocPath = join(homedir(), '.openclaw', 'openclaw.json');
|
|
79
|
+
const ocConfig = JSON.parse(readFileSync(ocPath, 'utf-8'));
|
|
80
|
+
const hasServer = !!ocConfig?.mcp?.servers?.['robot-resources-scraper'];
|
|
81
|
+
return {
|
|
82
|
+
healthy: hasServer,
|
|
83
|
+
detail: hasServer ? 'MCP registered' : 'scraper MCP not registered',
|
|
84
|
+
};
|
|
85
|
+
} catch {
|
|
86
|
+
return { healthy: false, detail: 'openclaw.json not found' };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function probePlatform(config) {
|
|
91
|
+
if (!config.api_key) {
|
|
92
|
+
return { healthy: false, detail: 'no API key configured' };
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
if (typeof fetch === 'undefined') {
|
|
96
|
+
return { healthy: false, detail: 'fetch unavailable' };
|
|
97
|
+
}
|
|
98
|
+
const platformUrl = process.env.RR_PLATFORM_URL || 'https://api.robotresources.ai';
|
|
99
|
+
const res = await fetch(`${platformUrl}/health`, {
|
|
100
|
+
signal: AbortSignal.timeout(PROBE_TIMEOUT_MS),
|
|
101
|
+
});
|
|
102
|
+
return {
|
|
103
|
+
healthy: res.ok,
|
|
104
|
+
detail: res.ok ? 'reachable' : `HTTP ${res.status}`,
|
|
105
|
+
};
|
|
106
|
+
} catch (err) {
|
|
107
|
+
const detail = err.name === 'AbortError' ? 'timeout' : 'unreachable';
|
|
108
|
+
return { healthy: false, detail };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function probeMcp() {
|
|
113
|
+
try {
|
|
114
|
+
const ocPath = join(homedir(), '.openclaw', 'openclaw.json');
|
|
115
|
+
const ocConfig = JSON.parse(readFileSync(ocPath, 'utf-8'));
|
|
116
|
+
const hasPlugin = !!ocConfig?.plugins?.entries?.['openclaw-plugin']?.enabled;
|
|
117
|
+
return {
|
|
118
|
+
healthy: hasPlugin,
|
|
119
|
+
detail: hasPlugin ? 'plugin registered' : 'openclaw-plugin not registered',
|
|
120
|
+
};
|
|
121
|
+
} catch {
|
|
122
|
+
return { healthy: false, detail: 'openclaw.json not found' };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Reads or creates a persistent machine identifier.
|
|
8
|
+
* Used for telemetry deduplication across sessions.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} [configDir] - Directory to store .machine-id (defaults to ~/.robot-resources)
|
|
11
|
+
* @returns {string} A UUID v4 machine identifier
|
|
12
|
+
*/
|
|
13
|
+
export function getOrCreateMachineId(configDir) {
|
|
14
|
+
const dir = configDir ?? join(homedir(), '.robot-resources');
|
|
15
|
+
const machineIdPath = join(dir, '.machine-id');
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const stored = readFileSync(machineIdPath, 'utf-8').trim();
|
|
19
|
+
if (stored) return stored;
|
|
20
|
+
} catch {
|
|
21
|
+
// File doesn't exist or can't be read — fall through to generate
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const machineId = randomUUID();
|
|
25
|
+
try {
|
|
26
|
+
mkdirSync(dir, { recursive: true });
|
|
27
|
+
writeFileSync(machineIdPath, machineId, 'utf-8');
|
|
28
|
+
} catch { /* non-fatal */ }
|
|
29
|
+
|
|
30
|
+
return machineId;
|
|
31
|
+
}
|
package/lib/tool-config.js
CHANGED
|
@@ -1,77 +1,58 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
-
import { readFileSync, writeFileSync, copyFileSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { readFileSync, writeFileSync, copyFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
5
|
import { join, dirname } from 'node:path';
|
|
6
6
|
import { isOpenClawInstalled, isOpenClawPluginInstalled, getOpenClawAuthMode } from './detect.js';
|
|
7
7
|
import { stripJson5 } from './json5.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* Prints immediately, then every 4s (safely under the 5s threshold).
|
|
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).
|
|
13
12
|
*/
|
|
14
|
-
function
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
proc.on('close', (code) => {
|
|
29
|
-
clearInterval(heartbeat);
|
|
30
|
-
if (code === 0) resolve();
|
|
31
|
-
else reject(new Error(`${cmd} ${args.join(' ')} exited with code ${code}`));
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
proc.on('error', (err) => {
|
|
35
|
-
clearInterval(heartbeat);
|
|
36
|
-
reject(err);
|
|
37
|
-
});
|
|
38
|
-
});
|
|
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));
|
|
39
26
|
}
|
|
40
27
|
|
|
41
28
|
/**
|
|
42
|
-
*
|
|
29
|
+
* Trust the Robot Resources plugin in OpenClaw config.
|
|
43
30
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
31
|
+
* Adds "openclaw-plugin" 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).
|
|
47
35
|
*
|
|
48
36
|
* Returns true if the config was updated, false otherwise.
|
|
49
37
|
*/
|
|
50
|
-
function
|
|
38
|
+
function trustPlugin() {
|
|
51
39
|
const configPath = join(homedir(), '.openclaw', 'openclaw.json');
|
|
52
40
|
|
|
53
41
|
try {
|
|
54
|
-
const
|
|
55
|
-
const config = JSON.parse(stripJson5(raw));
|
|
56
|
-
|
|
57
|
-
// Ensure agents.defaults.model exists
|
|
58
|
-
if (!config.agents) config.agents = {};
|
|
59
|
-
if (!config.agents.defaults) config.agents.defaults = {};
|
|
60
|
-
if (!config.agents.defaults.model) config.agents.defaults.model = {};
|
|
42
|
+
const config = readOrCreateOpenClawConfig();
|
|
61
43
|
|
|
62
|
-
|
|
44
|
+
if (!config.plugins) config.plugins = {};
|
|
45
|
+
if (!Array.isArray(config.plugins.allow)) config.plugins.allow = [];
|
|
63
46
|
|
|
64
|
-
|
|
65
|
-
if (currentPrimary && currentPrimary.startsWith('robot-resources/')) {
|
|
47
|
+
if (config.plugins.allow.includes('openclaw-plugin')) {
|
|
66
48
|
return false;
|
|
67
49
|
}
|
|
68
50
|
|
|
69
|
-
config.
|
|
51
|
+
config.plugins.allow.push('openclaw-plugin');
|
|
70
52
|
|
|
71
53
|
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
72
54
|
return true;
|
|
73
55
|
} catch {
|
|
74
|
-
// Config missing or malformed — non-fatal
|
|
75
56
|
return false;
|
|
76
57
|
}
|
|
77
58
|
}
|
|
@@ -89,8 +70,7 @@ function registerScraperMcp() {
|
|
|
89
70
|
const configPath = join(homedir(), '.openclaw', 'openclaw.json');
|
|
90
71
|
|
|
91
72
|
try {
|
|
92
|
-
const
|
|
93
|
-
const config = JSON.parse(stripJson5(raw));
|
|
73
|
+
const config = readOrCreateOpenClawConfig();
|
|
94
74
|
|
|
95
75
|
if (!config.mcp) config.mcp = {};
|
|
96
76
|
if (!config.mcp.servers) config.mcp.servers = {};
|
|
@@ -141,8 +121,7 @@ function registerPluginEntry() {
|
|
|
141
121
|
const configPath = join(homedir(), '.openclaw', 'openclaw.json');
|
|
142
122
|
|
|
143
123
|
try {
|
|
144
|
-
const
|
|
145
|
-
const config = JSON.parse(stripJson5(raw));
|
|
124
|
+
const config = readOrCreateOpenClawConfig();
|
|
146
125
|
|
|
147
126
|
if (!config.plugins) config.plugins = {};
|
|
148
127
|
if (!config.plugins.entries) config.plugins.entries = {};
|
|
@@ -188,9 +167,8 @@ function configureOpenClaw() {
|
|
|
188
167
|
// Register plugin in openclaw.json so OC loads it on gateway start.
|
|
189
168
|
registerPluginEntry();
|
|
190
169
|
|
|
191
|
-
//
|
|
192
|
-
|
|
193
|
-
const configActivated = activateRouterModel();
|
|
170
|
+
// Trust the plugin so OC loads it without provenance warnings.
|
|
171
|
+
const configActivated = trustPlugin();
|
|
194
172
|
|
|
195
173
|
return {
|
|
196
174
|
name: 'OpenClaw',
|
|
@@ -245,13 +223,20 @@ export function configureToolRouting() {
|
|
|
245
223
|
|
|
246
224
|
/**
|
|
247
225
|
* Restart the OpenClaw gateway so it picks up new plugin + config.
|
|
248
|
-
*
|
|
226
|
+
* Spawns a detached process that waits 3s then restarts — lets the wizard
|
|
227
|
+
* finish and return output to OC before the gateway dies.
|
|
228
|
+
*
|
|
229
|
+
* If called from inside an OC agent session (exec: npx robot-resources),
|
|
230
|
+
* a synchronous restart kills the session before output is captured.
|
|
231
|
+
* The deferred approach avoids this: wizard exits → OC captures output →
|
|
232
|
+
* 3s later gateway restarts → next conversation has all tools loaded.
|
|
249
233
|
*/
|
|
250
234
|
async function restartOpenClawGateway() {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
});
|
|
235
|
+
spawn('sh', ['-c', 'sleep 3 && openclaw gateway restart'], {
|
|
236
|
+
stdio: 'ignore',
|
|
237
|
+
detached: true,
|
|
238
|
+
}).unref();
|
|
239
|
+
process.stdout.write(' Restarting gateway...\n');
|
|
255
240
|
}
|
|
256
241
|
|
|
257
242
|
// Exported for testing and direct use
|
package/lib/wizard.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { homedir, hostname } from 'node:os';
|
|
1
4
|
import { readConfig, writeConfig } from '@robot-resources/cli-core/config.mjs';
|
|
2
5
|
import { findPython, isPortAvailable, isHeadless, isOpenClawInstalled } from './detect.js';
|
|
6
|
+
import { getOrCreateMachineId } from './machine-id.js';
|
|
3
7
|
import { setupRouter, isRouterInstalled, getVenvPythonPath } from './python-bridge.js';
|
|
4
8
|
import { installService, isServiceRunning, isServiceInstalled } from './service.js';
|
|
5
9
|
import { configureToolRouting, registerScraperMcp, restartOpenClawGateway } from './tool-config.js';
|
|
10
|
+
import { checkHealth } from './health-report.js';
|
|
6
11
|
import { header, step, success, warn, error, info, blank, summary } from './ui.js';
|
|
7
12
|
/**
|
|
8
13
|
* Main setup wizard. Handles the full onboarding flow:
|
|
@@ -36,24 +41,7 @@ export async function runWizard({ nonInteractive = false } = {}) {
|
|
|
36
41
|
const config = readConfig();
|
|
37
42
|
if (!config.api_key && !process.env.RR_API_KEY) {
|
|
38
43
|
try {
|
|
39
|
-
const
|
|
40
|
-
const { join } = await import('node:path');
|
|
41
|
-
const { homedir } = await import('node:os');
|
|
42
|
-
const { readFileSync, writeFileSync, mkdirSync } = await import('node:fs');
|
|
43
|
-
const { randomUUID } = await import('node:crypto');
|
|
44
|
-
|
|
45
|
-
const rrDir = join(homedir(), '.robot-resources');
|
|
46
|
-
const machineIdPath = join(rrDir, '.machine-id');
|
|
47
|
-
let machineId;
|
|
48
|
-
try {
|
|
49
|
-
machineId = readFileSync(machineIdPath, 'utf-8').trim();
|
|
50
|
-
} catch {
|
|
51
|
-
machineId = randomUUID();
|
|
52
|
-
try {
|
|
53
|
-
mkdirSync(rrDir, { recursive: true });
|
|
54
|
-
writeFileSync(machineIdPath, machineId, 'utf-8');
|
|
55
|
-
} catch { /* non-fatal */ }
|
|
56
|
-
}
|
|
44
|
+
const machineId = getOrCreateMachineId();
|
|
57
45
|
|
|
58
46
|
const platformUrl = process.env.RR_PLATFORM_URL || 'https://api.robotresources.ai';
|
|
59
47
|
const res = await fetch(`${platformUrl}/v1/auth/signup`, {
|
|
@@ -184,7 +172,7 @@ export async function runWizard({ nonInteractive = false } = {}) {
|
|
|
184
172
|
success(`${r.name}: already configured`);
|
|
185
173
|
} else if (r.action === 'installed') {
|
|
186
174
|
success(`${r.name}: plugin installed`);
|
|
187
|
-
if (r.configActivated) success(`${r.name}:
|
|
175
|
+
if (r.configActivated) success(`${r.name}: plugin trusted in openclaw.json`);
|
|
188
176
|
if (r.note) info(` ${r.note}`);
|
|
189
177
|
} else if (r.action === 'instructions') {
|
|
190
178
|
warn(`${r.name}: manual configuration needed:`);
|
|
@@ -218,10 +206,7 @@ export async function runWizard({ nonInteractive = false } = {}) {
|
|
|
218
206
|
} else {
|
|
219
207
|
// Either already registered, or no openclaw.json
|
|
220
208
|
try {
|
|
221
|
-
const
|
|
222
|
-
const { join: joinPath } = await import('node:path');
|
|
223
|
-
const { homedir: home } = await import('node:os');
|
|
224
|
-
const ocConfig = JSON.parse(readFs(joinPath(home(), '.openclaw', 'openclaw.json'), 'utf-8'));
|
|
209
|
+
const ocConfig = JSON.parse(readFileSync(join(homedir(), '.openclaw', 'openclaw.json'), 'utf-8'));
|
|
225
210
|
if (ocConfig?.mcp?.servers?.['robot-resources-scraper']) {
|
|
226
211
|
success('Scraper MCP already registered in OpenClaw');
|
|
227
212
|
results.scraper = true;
|
|
@@ -354,17 +339,52 @@ export async function runWizard({ nonInteractive = false } = {}) {
|
|
|
354
339
|
blank();
|
|
355
340
|
}
|
|
356
341
|
|
|
342
|
+
// ── Best-effort: Single gateway restart ────────────────────────────────
|
|
343
|
+
//
|
|
344
|
+
// Merged from two previous restarts (after plugin install + after scraper
|
|
345
|
+
// registration). Everything is already installed and registered above.
|
|
346
|
+
// If the session dies during this restart, the gateway picks up changes
|
|
347
|
+
// on its next natural restart.
|
|
348
|
+
|
|
349
|
+
if (isOpenClawInstalled() && (results.tools?.some(r => r.action === 'installed') || scraperRegistered)) {
|
|
350
|
+
try {
|
|
351
|
+
await restartOpenClawGateway();
|
|
352
|
+
success('Gateway restart scheduled — tools available in next conversation');
|
|
353
|
+
} catch {
|
|
354
|
+
warn('Gateway restart failed — tools may not be available until OpenClaw restarts.');
|
|
355
|
+
warn('Run manually: openclaw gateway restart');
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// ── Post-install health check ──────────────────────────────────────────
|
|
360
|
+
|
|
361
|
+
let healthReport = null;
|
|
362
|
+
if (somethingInstalled) {
|
|
363
|
+
try {
|
|
364
|
+
blank();
|
|
365
|
+
step('Running post-install health check...');
|
|
366
|
+
healthReport = await checkHealth();
|
|
367
|
+
|
|
368
|
+
if (healthReport.status === 'healthy') {
|
|
369
|
+
success(`Health: all components healthy`);
|
|
370
|
+
} else if (healthReport.status === 'partial') {
|
|
371
|
+
warn(`Health: ${healthReport.summary}`);
|
|
372
|
+
} else {
|
|
373
|
+
error(`Health: ${healthReport.summary}`);
|
|
374
|
+
}
|
|
375
|
+
} catch {
|
|
376
|
+
// Non-fatal — health check is informational
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
357
380
|
// ── Status file (persists results for agents that lose the session) ─────
|
|
358
381
|
|
|
359
382
|
if (somethingInstalled) {
|
|
360
383
|
try {
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
mkFs(statusDir, { recursive: true });
|
|
366
|
-
const pkgVersion = JSON.parse(readFs(new URL('../package.json', import.meta.url), 'utf-8')).version;
|
|
367
|
-
writeFs(joinP(statusDir, 'wizard-status.json'), JSON.stringify({
|
|
384
|
+
const statusDir = join(homedir(), '.robot-resources');
|
|
385
|
+
mkdirSync(statusDir, { recursive: true });
|
|
386
|
+
const pkgVersion = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf-8')).version;
|
|
387
|
+
writeFileSync(join(statusDir, 'wizard-status.json'), JSON.stringify({
|
|
368
388
|
completed_at: new Date().toISOString(),
|
|
369
389
|
version: pkgVersion,
|
|
370
390
|
router: results.router || false,
|
|
@@ -372,25 +392,10 @@ export async function runWizard({ nonInteractive = false } = {}) {
|
|
|
372
392
|
scraper: results.scraper || false,
|
|
373
393
|
plugin: results.tools?.some(r => r.action === 'installed') || false,
|
|
374
394
|
claim_url: results.claimUrl || readConfig().claim_url || null,
|
|
375
|
-
|
|
395
|
+
health_report: healthReport || null,
|
|
396
|
+
}, null, 2) + '\n', { mode: 0o600, encoding: 'utf-8' });
|
|
376
397
|
} catch {
|
|
377
398
|
// Non-fatal — status file is a convenience, not required
|
|
378
399
|
}
|
|
379
400
|
}
|
|
380
|
-
|
|
381
|
-
// ── Best-effort: Single gateway restart ────────────────────────────────
|
|
382
|
-
//
|
|
383
|
-
// Merged from two previous restarts (after plugin install + after scraper
|
|
384
|
-
// registration). Everything is already installed and registered above.
|
|
385
|
-
// If the session dies during this restart, the gateway picks up changes
|
|
386
|
-
// on its next natural restart.
|
|
387
|
-
|
|
388
|
-
if (isOpenClawInstalled() && (results.tools?.some(r => r.action === 'installed') || scraperRegistered)) {
|
|
389
|
-
try {
|
|
390
|
-
await restartOpenClawGateway();
|
|
391
|
-
success('OpenClaw gateway restarted');
|
|
392
|
-
} catch {
|
|
393
|
-
// Best-effort — gateway picks up changes on next restart
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
401
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "robot-resources",
|
|
3
|
-
"version": "1.7.
|
|
4
|
-
"description": "Robot Resources — AI agent
|
|
3
|
+
"version": "1.7.3",
|
|
4
|
+
"description": "Robot Resources — AI agent tools. One command to install everything.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"robot-resources-setup": "./bin/setup.js"
|