agenticros 0.5.4 → 0.5.6
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 +5 -2
- package/dist/__tests__/eyes.test.d.ts +2 -0
- package/dist/__tests__/eyes.test.d.ts.map +1 -0
- package/dist/__tests__/eyes.test.js +30 -0
- package/dist/__tests__/eyes.test.js.map +1 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +53 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/down.d.ts +3 -3
- package/dist/commands/down.js +4 -4
- package/dist/commands/down.js.map +1 -1
- package/dist/commands/eyes.d.ts +16 -0
- package/dist/commands/eyes.d.ts.map +1 -0
- package/dist/commands/eyes.js +113 -0
- package/dist/commands/eyes.js.map +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +14 -6
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/logs.d.ts +1 -0
- package/dist/commands/logs.d.ts.map +1 -1
- package/dist/commands/logs.js +2 -1
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/status.js +1 -1
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/up.d.ts +6 -0
- package/dist/commands/up.d.ts.map +1 -1
- package/dist/commands/up.js +8 -0
- package/dist/commands/up.js.map +1 -1
- package/dist/index.js +28 -3
- package/dist/index.js.map +1 -1
- package/dist/menu.d.ts.map +1 -1
- package/dist/menu.js +5 -0
- package/dist/menu.js.map +1 -1
- package/dist/util/eyes.d.ts +23 -0
- package/dist/util/eyes.d.ts.map +1 -0
- package/dist/util/eyes.js +53 -0
- package/dist/util/eyes.js.map +1 -0
- package/dist/util/pidfile.d.ts +1 -1
- package/dist/util/pidfile.d.ts.map +1 -1
- package/dist/util/pidfile.js.map +1 -1
- package/package.json +12 -11
- package/runtime/BUNDLE.json +1 -1
- package/runtime/README.md +4 -2
- package/runtime/docs/cli.md +15 -5
- package/runtime/docs/eyes.md +83 -0
- package/runtime/packages/robot-eyes/README.md +25 -0
- package/runtime/packages/robot-eyes/package.json +49 -0
- package/runtime/packages/robot-eyes/public/eyes.js +246 -0
- package/runtime/packages/robot-eyes/public/index.html +14 -0
- package/runtime/packages/robot-eyes/public/style.css +22 -0
- package/runtime/packages/robot-eyes/public/teleop.js +102 -0
- package/runtime/packages/robot-eyes/src/index.js +410 -0
- package/runtime/pnpm-lock.yaml +30 -1
- package/runtime/scripts/configure_agenticros.sh +0 -0
- package/runtime/scripts/configure_for_sim.sh +0 -0
- package/runtime/scripts/fix-openclaw-control-ui-path.sh +0 -0
- package/runtime/scripts/install_cli.sh +0 -0
- package/runtime/scripts/install_rosbridge_from_source.sh +0 -0
- package/runtime/scripts/onboard_robot.sh +0 -0
- package/runtime/scripts/pack-runtime.mjs +4 -3
- package/runtime/scripts/refresh-skill-deps.mjs +0 -0
- package/runtime/scripts/run_demo_native.sh +0 -0
- package/runtime/scripts/run_nemoclaw_host_stack.sh +0 -0
- package/runtime/scripts/run_robot_rosbridge.sh +0 -0
- package/runtime/scripts/setup_gateway_plugin.sh +0 -0
- package/runtime/scripts/setup_robot.sh +0 -0
- package/runtime/scripts/setup_workspace.sh +0 -0
- package/runtime/scripts/sim/run_sim.sh +0 -0
- package/runtime/scripts/smoke_test_nemoclaw.sh +0 -0
- package/runtime/scripts/start_demo.sh +0 -0
- package/runtime/scripts/use-openclaw-2026.2.26.sh +0 -0
- package/runtime/scripts/use-openclaw-2026.3.11.sh +0 -0
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @agenticros/eyes — fullscreen robot face driven by ROS 2 cmd_vel Twist.
|
|
4
|
+
*
|
|
5
|
+
* Subscribes to CMD_VEL_TOPIC for gaze (left/right turns). Optionally publishes
|
|
6
|
+
* the same topic from invisible WASD keyboard teleop (disabled with --no-teleop).
|
|
7
|
+
*
|
|
8
|
+
* Env (set by `agenticros eyes` from ~/.agenticros/config.json):
|
|
9
|
+
* CMD_VEL_TOPIC, MAX_LINEAR_VELOCITY, MAX_ANGULAR_VELOCITY, PORT, …
|
|
10
|
+
*/
|
|
11
|
+
import { createRequire } from "module";
|
|
12
|
+
import http from "http";
|
|
13
|
+
import fs from "fs";
|
|
14
|
+
import path from "path";
|
|
15
|
+
import { fileURLToPath } from "url";
|
|
16
|
+
import { spawn, execFileSync } from "child_process";
|
|
17
|
+
import { WebSocketServer } from "ws";
|
|
18
|
+
|
|
19
|
+
const require = createRequire(import.meta.url);
|
|
20
|
+
let rclnodejs;
|
|
21
|
+
try {
|
|
22
|
+
rclnodejs = require("rclnodejs");
|
|
23
|
+
} catch (e) {
|
|
24
|
+
console.error(
|
|
25
|
+
"Failed to load rclnodejs. Source ROS 2 and reinstall deps on the robot:\n" +
|
|
26
|
+
" source /opt/ros/<distro>/setup.bash && pnpm install --filter @agenticros/eyes\n" +
|
|
27
|
+
String(e instanceof Error ? e.message : e),
|
|
28
|
+
);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
33
|
+
const __dirname = path.dirname(__filename);
|
|
34
|
+
const PUBLIC_DIR = path.join(__dirname, "..", "public");
|
|
35
|
+
|
|
36
|
+
const PORT = Number(process.env.PORT || 8765);
|
|
37
|
+
const TOPIC = process.env.CMD_VEL_TOPIC || "/cmd_vel";
|
|
38
|
+
const ANGULAR_DEADZONE = Number(process.env.ANGULAR_DEADZONE || 0.05);
|
|
39
|
+
const CMD_TIMEOUT_MS = Number(process.env.CMD_TIMEOUT_MS || 300);
|
|
40
|
+
const NO_BROWSER = process.argv.includes("--no-browser");
|
|
41
|
+
const NO_TELEOP =
|
|
42
|
+
process.argv.includes("--no-teleop") ||
|
|
43
|
+
process.env.AGENTICROS_EYES_NO_TELEOP === "1";
|
|
44
|
+
|
|
45
|
+
const MAX_LINEAR = Number(process.env.MAX_LINEAR_VELOCITY || 1.0);
|
|
46
|
+
const MAX_ANGULAR = Number(process.env.MAX_ANGULAR_VELOCITY || 1.5);
|
|
47
|
+
|
|
48
|
+
const TELOP_LINEAR = Math.min(
|
|
49
|
+
Number(process.env.TELOP_LINEAR || 0.25),
|
|
50
|
+
MAX_LINEAR,
|
|
51
|
+
);
|
|
52
|
+
const TELOP_ANGULAR = Math.min(
|
|
53
|
+
Number(process.env.TELOP_ANGULAR || 0.9),
|
|
54
|
+
MAX_ANGULAR,
|
|
55
|
+
);
|
|
56
|
+
const TELOP_SCALE_STEP = Number(process.env.TELOP_SCALE_STEP || 0.15);
|
|
57
|
+
const TELOP_SCALE_MIN = Number(process.env.TELOP_SCALE_MIN || 0.2);
|
|
58
|
+
const TELOP_SCALE_MAX = Number(process.env.TELOP_SCALE_MAX || 3);
|
|
59
|
+
const TELOP_RATE_HZ = Number(process.env.TELOP_RATE_HZ || 20);
|
|
60
|
+
|
|
61
|
+
/** @type {{ gazeX: number, driving: boolean, lastCmdAt: number }} */
|
|
62
|
+
const state = {
|
|
63
|
+
gazeX: 0,
|
|
64
|
+
driving: false,
|
|
65
|
+
lastCmdAt: 0,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const teleop = {
|
|
69
|
+
/** @type {Map<object, { w: boolean, a: boolean, s: boolean, d: boolean }>} */
|
|
70
|
+
clients: new Map(),
|
|
71
|
+
scale: 1,
|
|
72
|
+
publishing: false,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
function mimeType(filePath) {
|
|
76
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
77
|
+
switch (ext) {
|
|
78
|
+
case ".html":
|
|
79
|
+
return "text/html; charset=utf-8";
|
|
80
|
+
case ".js":
|
|
81
|
+
return "application/javascript; charset=utf-8";
|
|
82
|
+
case ".css":
|
|
83
|
+
return "text/css; charset=utf-8";
|
|
84
|
+
case ".svg":
|
|
85
|
+
return "image/svg+xml";
|
|
86
|
+
case ".png":
|
|
87
|
+
return "image/png";
|
|
88
|
+
default:
|
|
89
|
+
return "application/octet-stream";
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function createHttpServer() {
|
|
94
|
+
return http.createServer((req, res) => {
|
|
95
|
+
const urlPath = decodeURIComponent((req.url || "/").split("?")[0]);
|
|
96
|
+
const rel = urlPath === "/" ? "/index.html" : urlPath;
|
|
97
|
+
const filePath = path.normalize(path.join(PUBLIC_DIR, rel));
|
|
98
|
+
|
|
99
|
+
if (!filePath.startsWith(PUBLIC_DIR)) {
|
|
100
|
+
res.writeHead(403).end("Forbidden");
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Gaze-only mode: serve HTML without the teleop script.
|
|
105
|
+
if (NO_TELEOP && rel === "/index.html") {
|
|
106
|
+
fs.readFile(filePath, "utf8", (err, html) => {
|
|
107
|
+
if (err) {
|
|
108
|
+
res.writeHead(404).end("Not found");
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const stripped = html.replace(
|
|
112
|
+
/\s*<script type="module" src="\/teleop\.js"><\/script>\s*/i,
|
|
113
|
+
"\n",
|
|
114
|
+
);
|
|
115
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
116
|
+
res.end(stripped);
|
|
117
|
+
});
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
fs.readFile(filePath, (err, data) => {
|
|
122
|
+
if (err) {
|
|
123
|
+
res.writeHead(404).end("Not found");
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
res.writeHead(200, { "Content-Type": mimeType(filePath) });
|
|
127
|
+
res.end(data);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function broadcast(wss, payload) {
|
|
133
|
+
const raw = JSON.stringify(payload);
|
|
134
|
+
for (const client of wss.clients) {
|
|
135
|
+
if (client.readyState === 1) {
|
|
136
|
+
client.send(raw);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function gazeFromTwist(msg) {
|
|
142
|
+
const z = msg?.angular?.z ?? 0;
|
|
143
|
+
if (Math.abs(z) < ANGULAR_DEADZONE) {
|
|
144
|
+
return { gazeX: 0, driving: false };
|
|
145
|
+
}
|
|
146
|
+
// +angular.z = left turn → eyes look right (screen +X)
|
|
147
|
+
return { gazeX: z > 0 ? 1 : -1, driving: true };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function mergedKeys() {
|
|
151
|
+
const keys = { w: false, a: false, s: false, d: false };
|
|
152
|
+
for (const k of teleop.clients.values()) {
|
|
153
|
+
keys.w ||= k.w;
|
|
154
|
+
keys.a ||= k.a;
|
|
155
|
+
keys.s ||= k.s;
|
|
156
|
+
keys.d ||= k.d;
|
|
157
|
+
}
|
|
158
|
+
return keys;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function clampTwist(twist) {
|
|
162
|
+
const { linear, angular } = twist;
|
|
163
|
+
const lx = linear?.x ?? 0;
|
|
164
|
+
const ly = linear?.y ?? 0;
|
|
165
|
+
const lz = linear?.z ?? 0;
|
|
166
|
+
const ax = angular?.x ?? 0;
|
|
167
|
+
const ay = angular?.y ?? 0;
|
|
168
|
+
const az = angular?.z ?? 0;
|
|
169
|
+
|
|
170
|
+
const linMag = Math.sqrt(lx * lx + ly * ly + lz * lz);
|
|
171
|
+
const scaleLin = linMag > MAX_LINEAR && linMag > 0 ? MAX_LINEAR / linMag : 1;
|
|
172
|
+
const angMag = Math.abs(az);
|
|
173
|
+
const scaleAng = angMag > MAX_ANGULAR && angMag > 0 ? MAX_ANGULAR / angMag : 1;
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
linear: { x: lx * scaleLin, y: ly * scaleLin, z: lz * scaleLin },
|
|
177
|
+
angular: {
|
|
178
|
+
x: ax * scaleAng,
|
|
179
|
+
y: ay * scaleAng,
|
|
180
|
+
z: Math.max(-MAX_ANGULAR, Math.min(MAX_ANGULAR, az)),
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function twistFromKeys(keys) {
|
|
186
|
+
const linear = TELOP_LINEAR * teleop.scale;
|
|
187
|
+
const angular = TELOP_ANGULAR * teleop.scale;
|
|
188
|
+
let x = 0;
|
|
189
|
+
let z = 0;
|
|
190
|
+
if (keys.w) x += linear;
|
|
191
|
+
if (keys.s) x -= linear;
|
|
192
|
+
if (keys.a) z += angular;
|
|
193
|
+
if (keys.d) z -= angular;
|
|
194
|
+
return clampTwist({
|
|
195
|
+
linear: { x, y: 0, z: 0 },
|
|
196
|
+
angular: { x: 0, y: 0, z },
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function anyKeyDown(keys) {
|
|
201
|
+
return keys.w || keys.a || keys.s || keys.d;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function findBrowser() {
|
|
205
|
+
const candidates = [
|
|
206
|
+
process.env.BROWSER,
|
|
207
|
+
"firefox",
|
|
208
|
+
"chromium-browser",
|
|
209
|
+
"chromium",
|
|
210
|
+
"google-chrome",
|
|
211
|
+
"google-chrome-stable",
|
|
212
|
+
].filter(Boolean);
|
|
213
|
+
|
|
214
|
+
for (const bin of candidates) {
|
|
215
|
+
try {
|
|
216
|
+
execFileSync("which", [bin], { stdio: "ignore" });
|
|
217
|
+
return bin;
|
|
218
|
+
} catch {
|
|
219
|
+
// try next
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function launchKiosk(url) {
|
|
226
|
+
const bin = findBrowser();
|
|
227
|
+
if (!bin) {
|
|
228
|
+
console.warn("No browser found. Open this URL fullscreen manually:", url);
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const args = bin.includes("firefox")
|
|
233
|
+
? ["--kiosk", url]
|
|
234
|
+
: ["--kiosk", "--noerrdialogs", "--disable-infobars", `--app=${url}`, url];
|
|
235
|
+
|
|
236
|
+
console.log(`Launching ${bin} in kiosk mode → ${url}`);
|
|
237
|
+
const child = spawn(bin, args, {
|
|
238
|
+
stdio: "ignore",
|
|
239
|
+
detached: true,
|
|
240
|
+
env: { ...process.env, DISPLAY: process.env.DISPLAY || ":0" },
|
|
241
|
+
});
|
|
242
|
+
child.unref();
|
|
243
|
+
child.on("error", (err) => {
|
|
244
|
+
console.warn(`Failed to launch ${bin}:`, err.message);
|
|
245
|
+
console.warn("Open this URL fullscreen manually:", url);
|
|
246
|
+
});
|
|
247
|
+
return child;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function main() {
|
|
251
|
+
await rclnodejs.init();
|
|
252
|
+
const node = rclnodejs.createNode("robot_eyes");
|
|
253
|
+
const publisher = NO_TELEOP
|
|
254
|
+
? null
|
|
255
|
+
: node.createPublisher("geometry_msgs/msg/Twist", TOPIC);
|
|
256
|
+
|
|
257
|
+
const server = createHttpServer();
|
|
258
|
+
const wss = new WebSocketServer({ server });
|
|
259
|
+
|
|
260
|
+
const publishStop = () => {
|
|
261
|
+
if (!publisher) return;
|
|
262
|
+
publisher.publish({
|
|
263
|
+
linear: { x: 0, y: 0, z: 0 },
|
|
264
|
+
angular: { x: 0, y: 0, z: 0 },
|
|
265
|
+
});
|
|
266
|
+
teleop.publishing = false;
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const tickTeleop = () => {
|
|
270
|
+
if (NO_TELEOP || !publisher) return;
|
|
271
|
+
const keys = mergedKeys();
|
|
272
|
+
if (!anyKeyDown(keys)) {
|
|
273
|
+
if (teleop.publishing) {
|
|
274
|
+
publishStop();
|
|
275
|
+
}
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
publisher.publish(twistFromKeys(keys));
|
|
279
|
+
teleop.publishing = true;
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
if (!NO_TELEOP) {
|
|
283
|
+
setInterval(tickTeleop, Math.max(10, Math.round(1000 / TELOP_RATE_HZ)));
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
wss.on("connection", (ws) => {
|
|
287
|
+
if (!NO_TELEOP) {
|
|
288
|
+
teleop.clients.set(ws, { w: false, a: false, s: false, d: false });
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
ws.send(
|
|
292
|
+
JSON.stringify({
|
|
293
|
+
type: "gaze",
|
|
294
|
+
gazeX: state.gazeX,
|
|
295
|
+
driving: state.driving,
|
|
296
|
+
}),
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
ws.on("message", (raw) => {
|
|
300
|
+
if (NO_TELEOP) return;
|
|
301
|
+
let msg;
|
|
302
|
+
try {
|
|
303
|
+
msg = JSON.parse(String(raw));
|
|
304
|
+
} catch {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (msg.type === "keys" && msg.keys && typeof msg.keys === "object") {
|
|
309
|
+
teleop.clients.set(ws, {
|
|
310
|
+
w: Boolean(msg.keys.w),
|
|
311
|
+
a: Boolean(msg.keys.a),
|
|
312
|
+
s: Boolean(msg.keys.s),
|
|
313
|
+
d: Boolean(msg.keys.d),
|
|
314
|
+
});
|
|
315
|
+
tickTeleop();
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (msg.type === "speed" && (msg.delta === 1 || msg.delta === -1)) {
|
|
320
|
+
const next = teleop.scale + msg.delta * TELOP_SCALE_STEP;
|
|
321
|
+
teleop.scale = Math.min(
|
|
322
|
+
TELOP_SCALE_MAX,
|
|
323
|
+
Math.max(TELOP_SCALE_MIN, next),
|
|
324
|
+
);
|
|
325
|
+
console.log(
|
|
326
|
+
`teleop speed scale: ${teleop.scale.toFixed(2)} ` +
|
|
327
|
+
`(linear≈${(TELOP_LINEAR * teleop.scale).toFixed(2)} m/s, ` +
|
|
328
|
+
`angular≈${(TELOP_ANGULAR * teleop.scale).toFixed(2)} rad/s)`,
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
ws.on("close", () => {
|
|
334
|
+
if (!NO_TELEOP) {
|
|
335
|
+
teleop.clients.delete(ws);
|
|
336
|
+
tickTeleop();
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
node.createSubscription("geometry_msgs/msg/Twist", TOPIC, (msg) => {
|
|
342
|
+
const next = gazeFromTwist(msg);
|
|
343
|
+
state.gazeX = next.gazeX;
|
|
344
|
+
state.driving = next.driving;
|
|
345
|
+
state.lastCmdAt = Date.now();
|
|
346
|
+
broadcast(wss, {
|
|
347
|
+
type: "gaze",
|
|
348
|
+
gazeX: state.gazeX,
|
|
349
|
+
driving: state.driving,
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
setInterval(() => {
|
|
354
|
+
if (!state.driving) return;
|
|
355
|
+
if (Date.now() - state.lastCmdAt < CMD_TIMEOUT_MS) return;
|
|
356
|
+
state.gazeX = 0;
|
|
357
|
+
state.driving = false;
|
|
358
|
+
broadcast(wss, {
|
|
359
|
+
type: "gaze",
|
|
360
|
+
gazeX: 0,
|
|
361
|
+
driving: false,
|
|
362
|
+
});
|
|
363
|
+
}, 50);
|
|
364
|
+
|
|
365
|
+
server.listen(PORT, "127.0.0.1", () => {
|
|
366
|
+
const url = `http://127.0.0.1:${PORT}/`;
|
|
367
|
+
console.log(`robot-eyes listening on ${url}`);
|
|
368
|
+
console.log(
|
|
369
|
+
`${NO_TELEOP ? "Subscribed" : "Subscribed + publishing"} ${TOPIC} ` +
|
|
370
|
+
`(deadzone=${ANGULAR_DEADZONE}, maxLin=${MAX_LINEAR}, maxAng=${MAX_ANGULAR})`,
|
|
371
|
+
);
|
|
372
|
+
if (NO_TELEOP) {
|
|
373
|
+
console.log("Keyboard teleop disabled (--no-teleop / gaze-only)");
|
|
374
|
+
} else {
|
|
375
|
+
console.log(
|
|
376
|
+
`Keyboard teleop: WASD drive, Q faster, Z slower ` +
|
|
377
|
+
`(base linear=${TELOP_LINEAR}, angular=${TELOP_ANGULAR})`,
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
if (!NO_BROWSER) {
|
|
381
|
+
launchKiosk(url);
|
|
382
|
+
} else {
|
|
383
|
+
console.log("--no-browser: open the URL yourself for fullscreen");
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
rclnodejs.spin(node);
|
|
388
|
+
|
|
389
|
+
const shutdown = async () => {
|
|
390
|
+
console.log("\nShutting down…");
|
|
391
|
+
try {
|
|
392
|
+
publishStop();
|
|
393
|
+
wss.close();
|
|
394
|
+
server.close();
|
|
395
|
+
node.destroy();
|
|
396
|
+
await rclnodejs.shutdown();
|
|
397
|
+
} catch {
|
|
398
|
+
// ignore
|
|
399
|
+
}
|
|
400
|
+
process.exit(0);
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
process.on("SIGINT", shutdown);
|
|
404
|
+
process.on("SIGTERM", shutdown);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
main().catch((err) => {
|
|
408
|
+
console.error(err);
|
|
409
|
+
process.exit(1);
|
|
410
|
+
});
|
package/runtime/pnpm-lock.yaml
CHANGED
|
@@ -157,6 +157,16 @@ importers:
|
|
|
157
157
|
specifier: ^5.7.0
|
|
158
158
|
version: 5.9.3
|
|
159
159
|
|
|
160
|
+
packages/robot-eyes:
|
|
161
|
+
dependencies:
|
|
162
|
+
ws:
|
|
163
|
+
specifier: ^8.18.0
|
|
164
|
+
version: 8.19.0
|
|
165
|
+
optionalDependencies:
|
|
166
|
+
rclnodejs:
|
|
167
|
+
specifier: ^1.9.0
|
|
168
|
+
version: 1.9.0
|
|
169
|
+
|
|
160
170
|
packages/ros-camera:
|
|
161
171
|
dependencies:
|
|
162
172
|
fzstd:
|
|
@@ -1687,6 +1697,11 @@ packages:
|
|
|
1687
1697
|
engines: {node: '>= 16.13.0'}
|
|
1688
1698
|
hasBin: true
|
|
1689
1699
|
|
|
1700
|
+
rclnodejs@1.9.0:
|
|
1701
|
+
resolution: {integrity: sha512-5nQ39WCnfzYoC1nppBaJR5bzfMHWrRRLGltsS6WqGb8KFJnguOCDMC9tZSxK4YU0oAtciG3/GZqDgZmfAxgUDQ==}
|
|
1702
|
+
engines: {node: '>= 16.13.0'}
|
|
1703
|
+
hasBin: true
|
|
1704
|
+
|
|
1690
1705
|
readable-stream@3.6.2:
|
|
1691
1706
|
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
|
|
1692
1707
|
engines: {node: '>= 6'}
|
|
@@ -1991,7 +2006,7 @@ snapshots:
|
|
|
1991
2006
|
zod: 3.25.76
|
|
1992
2007
|
optionalDependencies:
|
|
1993
2008
|
node-datachannel: 0.12.0
|
|
1994
|
-
rclnodejs: 1.
|
|
2009
|
+
rclnodejs: 1.9.0
|
|
1995
2010
|
transitivePeerDependencies:
|
|
1996
2011
|
- bufferutil
|
|
1997
2012
|
- jiti
|
|
@@ -3510,6 +3525,20 @@ snapshots:
|
|
|
3510
3525
|
- supports-color
|
|
3511
3526
|
optional: true
|
|
3512
3527
|
|
|
3528
|
+
rclnodejs@1.9.0:
|
|
3529
|
+
dependencies:
|
|
3530
|
+
'@rclnodejs/ref-array-di': 1.2.2
|
|
3531
|
+
'@rclnodejs/ref-struct-di': 1.1.1
|
|
3532
|
+
bindings: 1.5.0
|
|
3533
|
+
debug: 4.4.3
|
|
3534
|
+
json-bigint: 1.0.0
|
|
3535
|
+
node-addon-api: 8.6.0
|
|
3536
|
+
rxjs: 7.8.2
|
|
3537
|
+
walk: 2.3.15
|
|
3538
|
+
transitivePeerDependencies:
|
|
3539
|
+
- supports-color
|
|
3540
|
+
optional: true
|
|
3541
|
+
|
|
3513
3542
|
readable-stream@3.6.2:
|
|
3514
3543
|
dependencies:
|
|
3515
3544
|
inherits: 2.0.4
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
* What ships:
|
|
15
15
|
* - scripts/ shell helpers, install scripts, sim entrypoints
|
|
16
16
|
* - ros2_ws/src/agenticros_* our ROS 2 packages (msgs + Python nodes)
|
|
17
|
-
* - packages/{core,ros-camera,agenticros,agenticros-claude-code,agenticros-gemini}
|
|
18
|
-
* TypeScript sources for the workspace, NO dist or node_modules
|
|
17
|
+
* - packages/{core,ros-camera,agenticros,agenticros-claude-code,agenticros-gemini,robot-eyes}
|
|
18
|
+
* TypeScript/JS sources for the workspace, NO dist or node_modules
|
|
19
19
|
* - packages/agenticros-claude-code/dist (pre-built so `npx agenticros` works without colcon)
|
|
20
20
|
* - package.json / pnpm-workspace.yaml / tsconfig.base.json monorepo manifests
|
|
21
21
|
* - patches/ pnpm patchedDependencies (zenoh-ts, ...)
|
|
@@ -53,6 +53,7 @@ const INCLUDED_PACKAGES = [
|
|
|
53
53
|
"agenticros",
|
|
54
54
|
"agenticros-claude-code",
|
|
55
55
|
"agenticros-gemini",
|
|
56
|
+
"robot-eyes",
|
|
56
57
|
];
|
|
57
58
|
|
|
58
59
|
// NOTE: .npmrc is deliberately absent here. npm pack strips .npmrc from
|
|
@@ -67,7 +68,7 @@ const TOP_LEVEL_FILES = [
|
|
|
67
68
|
"README.md",
|
|
68
69
|
];
|
|
69
70
|
|
|
70
|
-
const DOCS_FILES = ["cli.md", "architecture.md", "robot-setup.md", "memory.md"];
|
|
71
|
+
const DOCS_FILES = ["cli.md", "architecture.md", "robot-setup.md", "memory.md", "eyes.md"];
|
|
71
72
|
|
|
72
73
|
const EXCLUDED_DIR_NAMES = new Set([
|
|
73
74
|
"node_modules",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|