infinicode 2.7.0 → 2.8.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/dist/cli.js +1 -4
- package/dist/commands/robopark.d.ts +3 -1
- package/dist/commands/robopark.js +45 -61
- package/dist/commands/serve.d.ts +0 -3
- package/dist/commands/serve.js +13 -23
- package/dist/kernel/federation/dashboard-html.d.ts +1 -1
- package/dist/kernel/federation/dashboard-html.js +35 -1
- package/dist/kernel/federation/federation.d.ts +5 -16
- package/dist/kernel/federation/federation.js +1 -7
- package/dist/kernel/federation/transport-http.d.ts +0 -14
- package/dist/kernel/federation/transport-http.js +3 -88
- package/dist/robopark/auto-start.d.ts +16 -0
- package/dist/robopark/auto-start.js +142 -0
- package/dist/robopark/discovery.d.ts +62 -0
- package/dist/robopark/discovery.js +144 -0
- package/dist/robopark/scan.d.ts +10 -0
- package/dist/robopark/scan.js +97 -0
- package/dist/robopark/serve.d.ts +13 -0
- package/dist/robopark/serve.js +142 -0
- package/dist/robopark/setup.d.ts +21 -0
- package/dist/robopark/setup.js +174 -0
- package/dist/robopark-cli.d.ts +0 -1
- package/dist/robopark-cli.js +84 -15
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -311,10 +311,7 @@ program
|
|
|
311
311
|
.option('--lan-port <port>', 'UDP discovery port for --lan (default 47915)')
|
|
312
312
|
.option('--tag <tag>', 'only discover peers carrying this tag (Tailscale ACL tag or LAN beacon tag)')
|
|
313
313
|
.option('--console', 'interactive slash-command console on the node (/nodes, /build, /activity …); on by default in a TTY')
|
|
314
|
-
.option('--
|
|
315
|
-
.option('--scheduler-url <url>', 'link a RoboPark scheduler so the dashboard shows sessions + telemetry (e.g. http://localhost:8080)')
|
|
316
|
-
.option('--scheduler-token <token>', 'bearer token to present to the scheduler on proxied requests')
|
|
317
|
-
.option('--tls-cert <path>', 'PEM cert file — serve the dashboard/mesh over HTTPS')
|
|
314
|
+
.option('--tls-cert <path>', 'PEM cert file — serve the mesh over HTTPS')
|
|
318
315
|
.option('--tls-key <path>', 'PEM private key file for --tls-cert')
|
|
319
316
|
.option('--gateway <url>', 'link to an InfiniBot gateway (ws://host:18789) — appear on its neural-mesh map')
|
|
320
317
|
.option('--gateway-token <token>', 'auth token for the InfiniBot gateway')
|
|
@@ -3,4 +3,6 @@ import type Conf from 'conf';
|
|
|
3
3
|
import type { InfinicodeConfig } from '../kernel/config-schema.js';
|
|
4
4
|
/** Attach the robopark verbs to a Commander command (the `robopark` program in
|
|
5
5
|
* the standalone bin, or the `infinicode robopark` group). */
|
|
6
|
-
export declare function attachRoboparkSubcommands(cmd: Command, config: Conf<InfinicodeConfig
|
|
6
|
+
export declare function attachRoboparkSubcommands(cmd: Command, config: Conf<InfinicodeConfig>, opts?: {
|
|
7
|
+
skipSetup?: boolean;
|
|
8
|
+
}): void;
|
|
@@ -111,10 +111,10 @@ function renderFleet(snap) {
|
|
|
111
111
|
return lines.join('\n');
|
|
112
112
|
}
|
|
113
113
|
const ROLE_PRESETS = {
|
|
114
|
-
scheduler: { mesh: 'hub', blurb: 'on-site hub (LiveKit Server 1) — bridges tailnet ↔ LAN,
|
|
115
|
-
robot: { mesh: 'satellite', blurb: 'a robot Pi — accept-only, LAN-discovered, never dials out',
|
|
116
|
-
laptop: { mesh: 'peer', blurb: 'your control station — drives the fleet over the mesh',
|
|
117
|
-
relay: { mesh: 'relay', blurb: 'a relay hop between two networks',
|
|
114
|
+
scheduler: { mesh: 'hub', blurb: 'on-site hub (LiveKit Server 1) — bridges tailnet ↔ LAN, runs robopark serve', lan: true, tailscale: true },
|
|
115
|
+
robot: { mesh: 'satellite', blurb: 'a robot Pi — accept-only, LAN-discovered, never dials out', lan: true, tailscale: false },
|
|
116
|
+
laptop: { mesh: 'peer', blurb: 'your control station — drives the fleet over the mesh', lan: false, tailscale: true },
|
|
117
|
+
relay: { mesh: 'relay', blurb: 'a relay hop between two networks', lan: true, tailscale: true },
|
|
118
118
|
};
|
|
119
119
|
// Power users can pass raw mesh roles too; fold them onto the matching preset.
|
|
120
120
|
const MESH_ALIASES = { hub: 'scheduler', satellite: 'robot', peer: 'laptop' };
|
|
@@ -192,11 +192,6 @@ async function roboparkSetup(config, opts) {
|
|
|
192
192
|
const { label, preset } = resolved;
|
|
193
193
|
const meshRole = preset.mesh;
|
|
194
194
|
// Preset defaults, overridable by explicit flags.
|
|
195
|
-
const dashboard = opts.dashboard ?? preset.dashboard;
|
|
196
|
-
// The RoboPark scheduler API runs on the same box as the on-site hub, so the
|
|
197
|
-
// scheduler role links it by default → the dashboard's Sessions & Telemetry
|
|
198
|
-
// tab is live out of the box. Other roles don't proxy a scheduler.
|
|
199
|
-
const schedulerUrl = opts.schedulerUrl ?? (label === 'scheduler' ? 'http://127.0.0.1:8080' : undefined);
|
|
200
195
|
const lan = opts.lan ?? preset.lan;
|
|
201
196
|
const tailscale = opts.tailscale ?? (preset.tailscale && !opts.hub); // seed URL replaces discovery
|
|
202
197
|
const port = opts.port ? parseInt(opts.port, 10) : DEFAULT_PORT;
|
|
@@ -232,8 +227,6 @@ async function roboparkSetup(config, opts) {
|
|
|
232
227
|
console.log(` lan: ${chalk.green('auto-discovery on')}`);
|
|
233
228
|
if (tailscale)
|
|
234
229
|
console.log(` tailnet:${chalk.green(' discovery on')}${opts.tag ? chalk.dim(' · tag ' + opts.tag) : ''}`);
|
|
235
|
-
if (dashboard)
|
|
236
|
-
console.log(` ui: ${chalk.green('dashboard on')}`);
|
|
237
230
|
console.log();
|
|
238
231
|
const serveOpts = {
|
|
239
232
|
role: meshRole,
|
|
@@ -244,33 +237,29 @@ async function roboparkSetup(config, opts) {
|
|
|
244
237
|
tailscale,
|
|
245
238
|
lan,
|
|
246
239
|
tag: opts.tag,
|
|
247
|
-
dashboard,
|
|
248
|
-
schedulerUrl,
|
|
249
|
-
schedulerToken: opts.schedulerToken,
|
|
250
240
|
gateway: opts.gateway,
|
|
251
241
|
gatewayToken: opts.gatewayToken,
|
|
252
242
|
};
|
|
253
|
-
|
|
254
|
-
console.log(chalk.dim(' starting node…\n'));
|
|
255
|
-
await serve(config, serveOpts);
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
const startCmd = [
|
|
243
|
+
const infinicodeCmd = [
|
|
259
244
|
'infinicode serve',
|
|
260
245
|
`--role ${meshRole}`,
|
|
261
|
-
dashboard ? '--dashboard' : '',
|
|
262
|
-
schedulerUrl ? `--scheduler-url ${schedulerUrl}` : '',
|
|
263
246
|
lan ? '--lan' : '',
|
|
264
247
|
tailscale ? '--tailscale' : '',
|
|
265
248
|
opts.tag ? `--tag ${opts.tag}` : '',
|
|
266
249
|
opts.hub ? `--seed ${opts.hub}` : '',
|
|
267
250
|
fed.token ? `--token ${fed.token}` : '',
|
|
268
251
|
opts.gateway ? `--gateway ${opts.gateway}` : '',
|
|
269
|
-
]
|
|
270
|
-
|
|
271
|
-
.
|
|
252
|
+
].filter(Boolean).join(' ');
|
|
253
|
+
if (opts.start) {
|
|
254
|
+
console.log(chalk.dim(' starting node…\n'));
|
|
255
|
+
await serve(config, serveOpts);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
272
258
|
console.log(chalk.dim(' saved. start it now with: ') + chalk.cyan('robopark setup --' + label + ' --start'));
|
|
273
|
-
console.log(chalk.dim(' or run the node directly: ') + chalk.cyan(
|
|
259
|
+
console.log(chalk.dim(' or run the node directly: ') + chalk.cyan(infinicodeCmd));
|
|
260
|
+
if (label === 'scheduler') {
|
|
261
|
+
console.log(chalk.dim(' and for the RoboPark UI: ') + chalk.cyan('robopark serve --port 8080'));
|
|
262
|
+
}
|
|
274
263
|
console.log();
|
|
275
264
|
}
|
|
276
265
|
async function roboparkFleet(config, opts) {
|
|
@@ -367,17 +356,13 @@ function openBrowser(url) {
|
|
|
367
356
|
}
|
|
368
357
|
}
|
|
369
358
|
async function roboparkDashboard(config, opts) {
|
|
370
|
-
|
|
359
|
+
// The RoboPark dashboard is now served by `robopark serve`, not the mesh node.
|
|
360
|
+
// Default to the scheduler URL if none is given, falling back to the local node.
|
|
361
|
+
const base = (opts.url ?? 'http://127.0.0.1:8080').replace(/\/+$/, '');
|
|
371
362
|
const token = authToken(config, opts.token);
|
|
372
363
|
const link = `${base}/${token ? `?token=${encodeURIComponent(token)}` : ''}`;
|
|
373
|
-
// Confirm the node is actually serving the dashboard before pointing there.
|
|
374
364
|
try {
|
|
375
|
-
const res = await fetch(
|
|
376
|
-
if (res.status === 404) {
|
|
377
|
-
console.log(chalk.yellow(` ⚠ node at ${base} is up but the dashboard is off.`));
|
|
378
|
-
console.log(chalk.dim(' restart it with infinicode serve --dashboard'));
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
365
|
+
const res = await fetch(`${base}/api/settings`, { headers: headers(token), signal: AbortSignal.timeout(5000) });
|
|
381
366
|
if (!res.ok)
|
|
382
367
|
throw new Error(`HTTP ${res.status}`);
|
|
383
368
|
}
|
|
@@ -393,33 +378,32 @@ async function roboparkDashboard(config, opts) {
|
|
|
393
378
|
// ── wiring ─────────────────────────────────────────────────────────────────────
|
|
394
379
|
/** Attach the robopark verbs to a Commander command (the `robopark` program in
|
|
395
380
|
* the standalone bin, or the `infinicode robopark` group). */
|
|
396
|
-
export function attachRoboparkSubcommands(cmd, config) {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
});
|
|
381
|
+
export function attachRoboparkSubcommands(cmd, config, opts) {
|
|
382
|
+
if (!opts?.skipSetup) {
|
|
383
|
+
cmd
|
|
384
|
+
.command('setup')
|
|
385
|
+
.description('Set up this device in one pass. Run bare for a guided wizard, or pass a role to skip it.')
|
|
386
|
+
.option('--scheduler', 'this device is the on-site hub (LiveKit Server 1)')
|
|
387
|
+
.option('--robot', 'this device is a talking-robot Raspberry Pi')
|
|
388
|
+
.option('--laptop', 'this device is your control station')
|
|
389
|
+
.option('--role <role>', 'explicit role: scheduler | robot | laptop | relay (or raw mesh role hub/peer/satellite)')
|
|
390
|
+
.option('--site <site>', 'physical location for fleet grouping, e.g. "Tel Aviv"')
|
|
391
|
+
.option('--name <name>', 'friendly device name (defaults to hostname)')
|
|
392
|
+
.option('--hub <url>', 'hub base URL to join, e.g. http://100.x.y.z:47913 (robots/laptops)')
|
|
393
|
+
.option('--gateway <url>', 'InfiniBot gateway ws:// URL to surface the fleet in InfiniBot (scheduler)')
|
|
394
|
+
.option('--gateway-token <token>', 'auth token for the InfiniBot gateway')
|
|
395
|
+
.option('--token <token>', 'shared bearer token every device must present')
|
|
396
|
+
.option('--port <port>', 'mesh listen port (default 47913)')
|
|
397
|
+
.option('--tag <tag>', 'only discover peers carrying this tag (e.g. tag:robopark)')
|
|
398
|
+
.option('--architecture <text>', "one-line brief of this device's job in the wider system")
|
|
399
|
+
.option('--lan', 'force zero-config LAN auto-discovery (on by default for scheduler/robot)')
|
|
400
|
+
.option('--tailscale', 'force Tailscale peer discovery (on by default for scheduler/laptop)')
|
|
401
|
+
.option('--start', 'launch the node now instead of just writing config')
|
|
402
|
+
.option('--yes', 'skip the guided wizard even with no role (non-interactive)')
|
|
403
|
+
.action(async (opts) => {
|
|
404
|
+
await roboparkSetup(config, opts);
|
|
405
|
+
});
|
|
406
|
+
}
|
|
423
407
|
cmd
|
|
424
408
|
.command('fleet')
|
|
425
409
|
.alias('status')
|
package/dist/commands/serve.d.ts
CHANGED
package/dist/commands/serve.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import chalk from 'chalk';
|
|
13
13
|
import { readFileSync } from 'node:fs';
|
|
14
|
-
import
|
|
14
|
+
import crypto from 'node:crypto';
|
|
15
15
|
import { fileURLToPath } from 'node:url';
|
|
16
16
|
import { dirname, join } from 'node:path';
|
|
17
17
|
import { buildKernelFromConfig, registerCloudProviders } from '../kernel/setup.js';
|
|
@@ -60,18 +60,20 @@ export async function serve(config, opts) {
|
|
|
60
60
|
// performSelfUpdate via the environment.
|
|
61
61
|
if (opts.supervised ?? fed.supervised)
|
|
62
62
|
process.env.INFINICODE_SUPERVISED = '1';
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
token
|
|
68
|
-
console.log(chalk.yellow(` ⚠ dashboard exposed on ${bindHost} without --token — generated a session token.`));
|
|
63
|
+
// Mesh nodes should always present a token on a routable interface. If none
|
|
64
|
+
// is provided, generate one so remote peers can authenticate.
|
|
65
|
+
if (!token && !isLoopback(bindHost)) {
|
|
66
|
+
token = crypto.randomBytes(9).toString('base64url');
|
|
67
|
+
console.log(chalk.yellow(` ⚠ mesh exposed on ${bindHost} without --token — generated a session token.`));
|
|
69
68
|
console.log(chalk.yellow(` peers must now connect with --token ${token}`));
|
|
70
69
|
}
|
|
71
70
|
// Keep a network-exposed, tokenless node read-only: it will answer status/map
|
|
72
71
|
// queries but refuse control commands until a token is set. Loopback-only or
|
|
73
72
|
// token-protected nodes are trusted and run everything.
|
|
74
73
|
const trusted = Boolean(token) || isLoopback(bindHost);
|
|
74
|
+
// commandGate is the mesh-side gate only. The RoboPark product UI is now
|
|
75
|
+
// served exclusively by `robopark serve`, so control writes there are no
|
|
76
|
+
// longer in scope for `infinicode serve`.
|
|
75
77
|
const commandGate = (input) => {
|
|
76
78
|
if (trusted)
|
|
77
79
|
return { ok: true };
|
|
@@ -158,13 +160,9 @@ export async function serve(config, opts) {
|
|
|
158
160
|
autoUpdate: opts.autoUpdate ?? fed.autoUpdate,
|
|
159
161
|
applyConfig,
|
|
160
162
|
autoPullConfig: role === 'satellite',
|
|
161
|
-
dashboard: opts.dashboard,
|
|
162
163
|
onApply,
|
|
163
164
|
commandGate,
|
|
164
165
|
tls,
|
|
165
|
-
schedulerUrl: opts.schedulerUrl ?? fed.schedulerUrl,
|
|
166
|
-
schedulerToken: opts.schedulerToken,
|
|
167
|
-
schedulerReadOnly: !trusted,
|
|
168
166
|
},
|
|
169
167
|
});
|
|
170
168
|
// Let the kernel's slash-command surface reach the mesh (/nodes, /node, …).
|
|
@@ -184,18 +182,10 @@ export async function serve(config, opts) {
|
|
|
184
182
|
console.log(` auth: ${chalk.green('token required')}`);
|
|
185
183
|
console.log();
|
|
186
184
|
await federation.start();
|
|
187
|
-
//
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const url = `${scheme}://${uiHost}:${port}/${token ? `?token=${token}` : ''}`;
|
|
192
|
-
console.log(chalk.bold(` dashboard: ${chalk.cyan(url)}`));
|
|
193
|
-
if (opts.schedulerUrl)
|
|
194
|
-
console.log(chalk.dim(` scheduler linked: ${opts.schedulerUrl} (sessions + telemetry tabs live)`));
|
|
195
|
-
if (!token)
|
|
196
|
-
console.log(chalk.dim(' (no token — anyone who can reach this port can control the fleet; add --token to lock it)'));
|
|
197
|
-
console.log();
|
|
198
|
-
}
|
|
185
|
+
// Mesh-only node. The RoboPark dashboard + scheduler UI are served by
|
|
186
|
+
// `robopark serve` on the hub box, not by `infinicode serve`.
|
|
187
|
+
console.log(chalk.dim(' (use `robopark serve` on the hub for the fleet dashboard + scheduler UI)'));
|
|
188
|
+
console.log();
|
|
199
189
|
// Connect to seeds. Satellites auto-source shared config from any hub/relay
|
|
200
190
|
// they connect to (handled inside federation.connect()).
|
|
201
191
|
for (const url of seeds) {
|