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 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('--dashboard', 'serve the RoboPark control web UI at http://<host>:<port>/ (fleet map, live activity, actions)')
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>): void;
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, serves the dashboard', dashboard: true, lan: true, tailscale: true },
115
- robot: { mesh: 'satellite', blurb: 'a robot Pi — accept-only, LAN-discovered, never dials out', dashboard: false, lan: true, tailscale: false },
116
- laptop: { mesh: 'peer', blurb: 'your control station — drives the fleet over the mesh', dashboard: false, lan: false, tailscale: true },
117
- relay: { mesh: 'relay', blurb: 'a relay hop between two networks', dashboard: false, lan: true, tailscale: true },
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
- if (opts.start) {
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
- .filter(Boolean)
271
- .join(' ');
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(startCmd));
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
- const base = nodeUrl(config, opts.url);
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(link, { headers: headers(token), signal: AbortSignal.timeout(5000) });
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
- cmd
398
- .command('setup')
399
- .description('Set up this device in one pass. Run bare for a guided wizard, or pass a role to skip it.')
400
- .option('--scheduler', 'this device is the on-site hub (LiveKit Server 1)')
401
- .option('--robot', 'this device is a talking-robot Raspberry Pi')
402
- .option('--laptop', 'this device is your control station')
403
- .option('--role <role>', 'explicit role: scheduler | robot | laptop | relay (or raw mesh role hub/peer/satellite)')
404
- .option('--site <site>', 'physical location for fleet grouping, e.g. "Tel Aviv"')
405
- .option('--name <name>', 'friendly device name (defaults to hostname)')
406
- .option('--hub <url>', 'hub base URL to join, e.g. http://100.x.y.z:47913 (robots/laptops)')
407
- .option('--gateway <url>', 'InfiniBot gateway ws:// URL to surface the fleet in InfiniBot (scheduler)')
408
- .option('--gateway-token <token>', 'auth token for the InfiniBot gateway')
409
- .option('--token <token>', 'shared bearer token every device must present')
410
- .option('--port <port>', 'mesh listen port (default 47913)')
411
- .option('--tag <tag>', 'only discover peers carrying this tag (e.g. tag:robopark)')
412
- .option('--architecture <text>', "one-line brief of this device's job in the wider system")
413
- .option('--lan', 'force zero-config LAN auto-discovery (on by default for scheduler/robot)')
414
- .option('--tailscale', 'force Tailscale peer discovery (on by default for scheduler/laptop)')
415
- .option('--dashboard', 'force the web control UI (on by default for scheduler)')
416
- .option('--scheduler-url <url>', 'RoboPark scheduler API to link for the dashboard Sessions tab (default http://127.0.0.1:8080 for the scheduler role)')
417
- .option('--scheduler-token <token>', 'bearer token presented to the scheduler on proxied requests')
418
- .option('--start', 'launch the node now instead of just writing config')
419
- .option('--yes', 'skip the guided wizard even with no role (non-interactive)')
420
- .action(async (opts) => {
421
- await roboparkSetup(config, opts);
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')
@@ -17,9 +17,6 @@ export interface ServeOptions {
17
17
  gateway?: string;
18
18
  gatewayToken?: string;
19
19
  gatewayPassword?: string;
20
- dashboard?: boolean;
21
- schedulerUrl?: string;
22
- schedulerToken?: string;
23
20
  tlsCert?: string;
24
21
  tlsKey?: string;
25
22
  supervised?: boolean;
@@ -11,7 +11,7 @@
11
11
  */
12
12
  import chalk from 'chalk';
13
13
  import { readFileSync } from 'node:fs';
14
- import { randomBytes } from 'node:crypto';
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
- // Secure-by-default: a control dashboard exposed on a routable interface with
64
- // no token is a wide-open remote. If the operator asked for the dashboard but
65
- // set no token, mint a session token so the surface isn't unauthenticated.
66
- if (opts.dashboard && !token && !isLoopback(bindHost)) {
67
- token = randomBytes(9).toString('base64url');
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
- // Control dashboard link prefer a reachable host over the 0.0.0.0 wildcard.
188
- if (opts.dashboard) {
189
- const uiHost = isLoopback(bindHost) || bindHost === '0.0.0.0' ? 'localhost' : bindHost;
190
- const scheme = tls ? 'https' : 'http';
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) {