infinicode 2.3.6 → 2.5.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.
Files changed (34) hide show
  1. package/bin/robopark.js +2 -0
  2. package/dist/cli.js +26 -1
  3. package/dist/commands/maintain.d.ts +29 -0
  4. package/dist/commands/maintain.js +186 -0
  5. package/dist/commands/mcp.js +1 -1
  6. package/dist/commands/robopark.d.ts +6 -0
  7. package/dist/commands/robopark.js +450 -0
  8. package/dist/commands/serve.d.ts +5 -0
  9. package/dist/commands/serve.js +170 -11
  10. package/dist/kernel/agents/backends/cli-backend.d.ts +4 -0
  11. package/dist/kernel/agents/backends/cli-backend.js +13 -5
  12. package/dist/kernel/agents/backends/detect.d.ts +12 -0
  13. package/dist/kernel/agents/backends/detect.js +102 -2
  14. package/dist/kernel/agents/backends/profiles.d.ts +67 -0
  15. package/dist/kernel/agents/backends/profiles.js +48 -0
  16. package/dist/kernel/agents/backends/registry.d.ts +2 -1
  17. package/dist/kernel/agents/backends/registry.js +39 -5
  18. package/dist/kernel/agents/backends/rotation.d.ts +51 -0
  19. package/dist/kernel/agents/backends/rotation.js +107 -0
  20. package/dist/kernel/agents/backends/types.d.ts +3 -1
  21. package/dist/kernel/federation/dashboard-html.d.ts +13 -0
  22. package/dist/kernel/federation/dashboard-html.js +561 -0
  23. package/dist/kernel/federation/federation.d.ts +75 -1
  24. package/dist/kernel/federation/federation.js +119 -23
  25. package/dist/kernel/federation/moltfed-adapter.js +1 -0
  26. package/dist/kernel/federation/telemetry.d.ts +1 -1
  27. package/dist/kernel/federation/telemetry.js +2 -1
  28. package/dist/kernel/federation/transport-http.d.ts +34 -1
  29. package/dist/kernel/federation/transport-http.js +117 -4
  30. package/dist/kernel/federation/types.d.ts +20 -0
  31. package/dist/kernel/mcp/mcp-server.js +23 -7
  32. package/dist/robopark-cli.d.ts +2 -0
  33. package/dist/robopark-cli.js +23 -0
  34. package/package.json +7 -3
@@ -0,0 +1,450 @@
1
+ /**
2
+ * robopark — the RoboPark fleet control CLI.
3
+ *
4
+ * A thin operator layer that rides the infinicode mesh instead of SSH-ing around:
5
+ *
6
+ * robopark setup --role scheduler --hub http://100.x:47913 --start
7
+ * robopark fleet # live table of every node the hub can see
8
+ * robopark run panda-a /status # run a command on one node
9
+ * robopark apply # push shared config (providers/policy) to the fleet
10
+ * robopark dashboard --open # open the web control UI
11
+ *
12
+ * `setup` writes this device's role + mesh config (the nine manual steps,
13
+ * automated). The other verbs talk to a running node's HTTP API (default the
14
+ * local node at 127.0.0.1:<port>), so the CLI never needs its own transport —
15
+ * it reuses the mesh the node already stood up.
16
+ */
17
+ import chalk from 'chalk';
18
+ import { spawn } from 'node:child_process';
19
+ import { saveRole } from '../kernel/federation/role-context.js';
20
+ import { serve } from './serve.js';
21
+ const DEFAULT_PORT = 47913;
22
+ function nodeUrl(config, urlOpt) {
23
+ if (urlOpt)
24
+ return urlOpt.replace(/\/+$/, '');
25
+ const fed = (config.get('federation') ?? {});
26
+ return `http://127.0.0.1:${fed.port ?? DEFAULT_PORT}`;
27
+ }
28
+ function authToken(config, tokenOpt) {
29
+ const fed = (config.get('federation') ?? {});
30
+ return tokenOpt ?? fed.token;
31
+ }
32
+ function headers(token) {
33
+ const h = { 'content-type': 'application/json' };
34
+ if (token)
35
+ h['authorization'] = `Bearer ${token}`;
36
+ return h;
37
+ }
38
+ async function getStatus(url, token) {
39
+ const res = await fetch(`${url}/fed/status`, { headers: headers(token), signal: AbortSignal.timeout(6000) });
40
+ if (res.status === 401)
41
+ throw new Error('unauthorized — this node requires a token (pass --token <secret>)');
42
+ if (!res.ok)
43
+ throw new Error(`node returned ${res.status}`);
44
+ return (await res.json());
45
+ }
46
+ function unreachable(url, err) {
47
+ const msg = err instanceof Error ? err.message : String(err);
48
+ return [
49
+ chalk.red(` ✗ no mesh node reachable at ${url}`),
50
+ chalk.dim(` ${msg}`),
51
+ chalk.dim(' start one with infinicode serve --dashboard or robopark setup --start'),
52
+ ].join('\n');
53
+ }
54
+ // ── loadbar / formatting helpers ───────────────────────────────────────────────
55
+ function bar(pct, width = 10) {
56
+ const v = Math.max(0, Math.min(100, Math.round(pct)));
57
+ const filled = Math.round((v / 100) * width);
58
+ const glyph = '█'.repeat(filled) + '░'.repeat(width - filled);
59
+ const color = v >= 80 ? chalk.red : v >= 50 ? chalk.yellow : chalk.green;
60
+ return color(glyph);
61
+ }
62
+ function pad(s, n) {
63
+ const len = [...s].length;
64
+ return len >= n ? s : s + ' '.repeat(n - len);
65
+ }
66
+ /** Pad the visible text to `width`, THEN colorize — so ANSI codes never count
67
+ * toward column width (the bug you get from padding an already-colored string). */
68
+ function cell(text, width, color) {
69
+ const p = pad(text, width);
70
+ return color ? color(p) : p;
71
+ }
72
+ function renderFleet(snap) {
73
+ const nodes = snap.nodes ?? [];
74
+ const lines = [];
75
+ const up = nodes.filter(n => n.connected).length;
76
+ const runs = (snap.runs ?? []).filter(r => r.status === 'running').length;
77
+ lines.push('');
78
+ lines.push(chalk.bold(' RoboPark fleet') + chalk.dim(` ${up}/${nodes.length} up · ${runs} active run${runs === 1 ? '' : 's'}`));
79
+ lines.push(chalk.dim(' ' + '─'.repeat(72)));
80
+ if (!nodes.length) {
81
+ lines.push(chalk.dim(' no nodes reporting yet — bring peers online with `infinicode serve`.'));
82
+ return lines.join('\n');
83
+ }
84
+ const W = { name: 22, role: 11, state: 8, load: 16, site: 12 };
85
+ lines.push(' ' +
86
+ chalk.dim(pad('NODE', W.name) + pad('ROLE', W.role) + pad('STATE', W.state) + pad('LOAD', W.load) + pad('SITE', W.site) + 'VER'));
87
+ nodes.sort((a, b) => Number(b.connected) - Number(a.connected));
88
+ for (const n of nodes) {
89
+ const isSelf = snap.self && snap.self.nodeId === n.nodeId;
90
+ const name = n.displayName + (isSelf ? ' ‹this›' : '');
91
+ const state = cell(n.connected ? '● up' : '○ down', W.state, n.connected ? chalk.green : chalk.dim);
92
+ // LOAD: colored bar (fixed 10 visible glyphs) + percent, hand-padded so the
93
+ // bar's ANSI codes don't skew the column.
94
+ let load;
95
+ if (n.connected) {
96
+ const pct = `${Math.round(n.load ?? 0)}%`;
97
+ const visible = 10 + 1 + pct.length; // bar + space + "NN%"
98
+ load = `${bar(n.load ?? 0)} ${pct}` + ' '.repeat(Math.max(0, W.load - visible));
99
+ }
100
+ else {
101
+ load = cell('—', W.load, chalk.dim);
102
+ }
103
+ lines.push(' ' +
104
+ cell(name, W.name, isSelf ? chalk.bold.cyan : chalk.bold) +
105
+ cell(n.role, W.role, chalk.yellow) +
106
+ state +
107
+ load +
108
+ cell(n.site ?? '-', W.site, n.site ? undefined : chalk.dim) +
109
+ chalk.dim('v' + (n.version ?? '?')));
110
+ }
111
+ return lines.join('\n');
112
+ }
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 },
118
+ };
119
+ // Power users can pass raw mesh roles too; fold them onto the matching preset.
120
+ const MESH_ALIASES = { hub: 'scheduler', satellite: 'robot', peer: 'laptop' };
121
+ function resolveRole(input) {
122
+ if (!input)
123
+ return null;
124
+ const key = input.toLowerCase();
125
+ const label = MESH_ALIASES[key] ?? key;
126
+ const preset = ROLE_PRESETS[label];
127
+ return preset ? { label, preset } : null;
128
+ }
129
+ /** Interactive fallback — the streamlined "just run `robopark setup`" path. */
130
+ async function setupWizard(opts) {
131
+ const inquirer = (await import('inquirer')).default;
132
+ const a = (await inquirer.prompt([
133
+ {
134
+ type: 'list',
135
+ name: 'role',
136
+ message: 'What is this device?',
137
+ choices: [
138
+ { name: 'scheduler — the on-site hub (LiveKit Server 1)', value: 'scheduler' },
139
+ { name: 'robot — a talking-robot Raspberry Pi', value: 'robot' },
140
+ { name: 'laptop — your control station (drives the fleet)', value: 'laptop' },
141
+ ],
142
+ default: opts.robot ? 'robot' : opts.laptop ? 'laptop' : 'scheduler',
143
+ },
144
+ { type: 'input', name: 'name', message: 'Name for this device:', default: opts.name },
145
+ { type: 'input', name: 'site', message: 'Site / location (optional):', default: opts.site ?? '' },
146
+ {
147
+ type: 'input',
148
+ name: 'hub',
149
+ message: 'Hub URL to join (blank if this IS the scheduler):',
150
+ default: opts.hub ?? '',
151
+ when: (ans) => ans.role !== 'scheduler',
152
+ },
153
+ {
154
+ type: 'input',
155
+ name: 'gateway',
156
+ message: 'InfiniBot gateway ws:// URL (optional — surfaces the fleet in InfiniBot):',
157
+ default: opts.gateway ?? '',
158
+ when: (ans) => ans.role === 'scheduler',
159
+ },
160
+ { type: 'input', name: 'token', message: 'Shared token every device presents (optional but recommended):', default: opts.token ?? '' },
161
+ { type: 'confirm', name: 'start', message: 'Start the node now?', default: true },
162
+ ]));
163
+ return {
164
+ ...opts,
165
+ role: a.role,
166
+ name: a.name || opts.name,
167
+ site: a.site || undefined,
168
+ hub: a.hub || opts.hub,
169
+ gateway: a.gateway || opts.gateway,
170
+ token: a.token || opts.token,
171
+ start: a.start,
172
+ };
173
+ }
174
+ async function roboparkSetup(config, opts) {
175
+ // Shorthand flags → --role.
176
+ const roleInput = opts.role ?? (opts.scheduler ? 'scheduler' : opts.robot ? 'robot' : opts.laptop ? 'laptop' : undefined);
177
+ // No role given? Run the guided wizard in a TTY (unless --yes); otherwise error.
178
+ if (!roleInput && !opts.yes) {
179
+ if (process.stdin.isTTY) {
180
+ opts = await setupWizard(opts);
181
+ }
182
+ else {
183
+ console.log(chalk.red(' robopark setup: pass --scheduler | --robot | --laptop (or --role <role>)'));
184
+ return;
185
+ }
186
+ }
187
+ const resolved = resolveRole(opts.role ?? roleInput ?? 'laptop');
188
+ if (!resolved) {
189
+ console.log(chalk.red(` unknown role "${opts.role}". Use: scheduler | robot | laptop | relay`));
190
+ return;
191
+ }
192
+ const { label, preset } = resolved;
193
+ const meshRole = preset.mesh;
194
+ // Preset defaults, overridable by explicit flags.
195
+ const dashboard = opts.dashboard ?? preset.dashboard;
196
+ const lan = opts.lan ?? preset.lan;
197
+ const tailscale = opts.tailscale ?? (preset.tailscale && !opts.hub); // seed URL replaces discovery
198
+ const port = opts.port ? parseInt(opts.port, 10) : DEFAULT_PORT;
199
+ const prev = (config.get('federation') ?? {});
200
+ const fed = {
201
+ ...prev,
202
+ enabled: true,
203
+ role: meshRole,
204
+ port,
205
+ displayName: opts.name ?? prev.displayName,
206
+ token: opts.token ?? prev.token,
207
+ seeds: opts.hub ? Array.from(new Set([...(prev.seeds ?? []), opts.hub])) : prev.seeds,
208
+ lan,
209
+ };
210
+ config.set('federation', fed);
211
+ // Persist the device's role/site brief — injected into every task run here.
212
+ const roleCtx = saveRole({
213
+ role: opts.name ?? label,
214
+ site: opts.site,
215
+ architecture: opts.architecture,
216
+ });
217
+ console.log(chalk.bold('\n robopark setup'));
218
+ console.log(chalk.dim(' ' + '─'.repeat(52)));
219
+ console.log(` device: ${chalk.yellow(label)} ${chalk.dim('→ mesh role ' + meshRole)}`);
220
+ console.log(chalk.dim(` ${preset.blurb}`));
221
+ console.log(` name: ${chalk.cyan(opts.name ?? '(hostname)')}${opts.site ? chalk.dim(' @ ' + opts.site) : ''}`);
222
+ console.log(` listen: ${chalk.cyan('0.0.0.0:' + port)}`);
223
+ if (opts.hub)
224
+ console.log(` hub: ${chalk.cyan(opts.hub)}`);
225
+ if (fed.token)
226
+ console.log(` auth: ${chalk.green('token set')}`);
227
+ if (lan)
228
+ console.log(` lan: ${chalk.green('auto-discovery on')}`);
229
+ if (tailscale)
230
+ console.log(` tailnet:${chalk.green(' discovery on')}${opts.tag ? chalk.dim(' · tag ' + opts.tag) : ''}`);
231
+ if (dashboard)
232
+ console.log(` ui: ${chalk.green('dashboard on')}`);
233
+ console.log();
234
+ const serveOpts = {
235
+ role: meshRole,
236
+ name: opts.name,
237
+ port: String(port),
238
+ token: fed.token,
239
+ seed: opts.hub ? [opts.hub] : undefined,
240
+ tailscale,
241
+ lan,
242
+ tag: opts.tag,
243
+ dashboard,
244
+ gateway: opts.gateway,
245
+ gatewayToken: opts.gatewayToken,
246
+ };
247
+ if (opts.start) {
248
+ console.log(chalk.dim(' starting node…\n'));
249
+ await serve(config, serveOpts);
250
+ return;
251
+ }
252
+ const startCmd = [
253
+ 'infinicode serve',
254
+ `--role ${meshRole}`,
255
+ dashboard ? '--dashboard' : '',
256
+ lan ? '--lan' : '',
257
+ tailscale ? '--tailscale' : '',
258
+ opts.tag ? `--tag ${opts.tag}` : '',
259
+ opts.hub ? `--seed ${opts.hub}` : '',
260
+ fed.token ? `--token ${fed.token}` : '',
261
+ opts.gateway ? `--gateway ${opts.gateway}` : '',
262
+ ]
263
+ .filter(Boolean)
264
+ .join(' ');
265
+ console.log(chalk.dim(' saved. start it now with: ') + chalk.cyan('robopark setup --' + label + ' --start'));
266
+ console.log(chalk.dim(' or run the node directly: ') + chalk.cyan(startCmd));
267
+ console.log();
268
+ }
269
+ async function roboparkFleet(config, opts) {
270
+ const url = nodeUrl(config, opts.url);
271
+ const token = authToken(config, opts.token);
272
+ const once = async () => {
273
+ let snap;
274
+ try {
275
+ snap = await getStatus(url, token);
276
+ }
277
+ catch (err) {
278
+ console.log(unreachable(url, err));
279
+ return false;
280
+ }
281
+ if (opts.json) {
282
+ console.log(JSON.stringify(snap, null, 2));
283
+ }
284
+ else {
285
+ if (opts.watch)
286
+ process.stdout.write('\x1b[2J\x1b[H');
287
+ console.log(renderFleet(snap));
288
+ if (opts.watch)
289
+ console.log(chalk.dim(`\n updated ${new Date().toLocaleTimeString()} · Ctrl-C to stop`));
290
+ }
291
+ return true;
292
+ };
293
+ if (!opts.watch) {
294
+ await once();
295
+ return;
296
+ }
297
+ const ok = await once();
298
+ if (!ok)
299
+ return; // don't spin on an unreachable node
300
+ const timer = setInterval(() => void once(), 2000);
301
+ await new Promise(resolve => {
302
+ process.on('SIGINT', () => {
303
+ clearInterval(timer);
304
+ resolve();
305
+ });
306
+ });
307
+ }
308
+ async function roboparkRun(config, node, command, opts) {
309
+ const url = nodeUrl(config, opts.url);
310
+ const token = authToken(config, opts.token);
311
+ const cmd = command.join(' ').trim();
312
+ if (!cmd) {
313
+ console.log(chalk.red(' nothing to run — usage: robopark run <node> <command…>'));
314
+ return;
315
+ }
316
+ // Local node keywords bypass the /node dispatch wrapper.
317
+ const local = ['.', 'self', 'local', 'here'].includes(node.toLowerCase());
318
+ const input = local ? (cmd.startsWith('/') ? cmd : `/${cmd}`) : `/node ${node} ${cmd}`;
319
+ try {
320
+ const res = await fetch(`${url}/fed/command`, {
321
+ method: 'POST',
322
+ headers: headers(token),
323
+ body: JSON.stringify({ input }),
324
+ signal: AbortSignal.timeout(120_000),
325
+ });
326
+ const body = (await res.json().catch(() => ({ ok: false, text: `HTTP ${res.status}` })));
327
+ const text = body.text ?? JSON.stringify(body);
328
+ console.log(body.ok === false ? chalk.yellow(text) : text);
329
+ }
330
+ catch (err) {
331
+ console.log(unreachable(url, err));
332
+ }
333
+ }
334
+ async function roboparkApply(config, opts) {
335
+ const url = nodeUrl(config, opts.url);
336
+ const token = authToken(config, opts.token);
337
+ try {
338
+ const res = await fetch(`${url}/fed/apply`, {
339
+ method: 'POST',
340
+ headers: headers(token),
341
+ body: '{}',
342
+ signal: AbortSignal.timeout(15_000),
343
+ });
344
+ const body = (await res.json().catch(() => ({ ok: false, text: `HTTP ${res.status}` })));
345
+ console.log((body.ok === false ? chalk.yellow : chalk.green)(' ' + (body.text ?? 'applied')));
346
+ }
347
+ catch (err) {
348
+ console.log(unreachable(url, err));
349
+ }
350
+ }
351
+ function openBrowser(url) {
352
+ const platform = process.platform;
353
+ const cmd = platform === 'win32' ? 'cmd' : platform === 'darwin' ? 'open' : 'xdg-open';
354
+ const args = platform === 'win32' ? ['/c', 'start', '', url] : [url];
355
+ try {
356
+ spawn(cmd, args, { detached: true, stdio: 'ignore' }).unref();
357
+ }
358
+ catch {
359
+ /* fall back to just printing */
360
+ }
361
+ }
362
+ async function roboparkDashboard(config, opts) {
363
+ const base = nodeUrl(config, opts.url);
364
+ const token = authToken(config, opts.token);
365
+ const link = `${base}/${token ? `?token=${encodeURIComponent(token)}` : ''}`;
366
+ // Confirm the node is actually serving the dashboard before pointing there.
367
+ try {
368
+ const res = await fetch(link, { headers: headers(token), signal: AbortSignal.timeout(5000) });
369
+ if (res.status === 404) {
370
+ console.log(chalk.yellow(` ⚠ node at ${base} is up but the dashboard is off.`));
371
+ console.log(chalk.dim(' restart it with infinicode serve --dashboard'));
372
+ return;
373
+ }
374
+ if (!res.ok)
375
+ throw new Error(`HTTP ${res.status}`);
376
+ }
377
+ catch (err) {
378
+ console.log(unreachable(base, err));
379
+ return;
380
+ }
381
+ console.log(chalk.bold('\n RoboPark control dashboard'));
382
+ console.log(' ' + chalk.cyan(link) + '\n');
383
+ if (opts.open)
384
+ openBrowser(link);
385
+ }
386
+ // ── wiring ─────────────────────────────────────────────────────────────────────
387
+ /** Attach the robopark verbs to a Commander command (the `robopark` program in
388
+ * the standalone bin, or the `infinicode robopark` group). */
389
+ export function attachRoboparkSubcommands(cmd, config) {
390
+ cmd
391
+ .command('setup')
392
+ .description('Set up this device in one pass. Run bare for a guided wizard, or pass a role to skip it.')
393
+ .option('--scheduler', 'this device is the on-site hub (LiveKit Server 1)')
394
+ .option('--robot', 'this device is a talking-robot Raspberry Pi')
395
+ .option('--laptop', 'this device is your control station')
396
+ .option('--role <role>', 'explicit role: scheduler | robot | laptop | relay (or raw mesh role hub/peer/satellite)')
397
+ .option('--site <site>', 'physical location for fleet grouping, e.g. "Tel Aviv"')
398
+ .option('--name <name>', 'friendly device name (defaults to hostname)')
399
+ .option('--hub <url>', 'hub base URL to join, e.g. http://100.x.y.z:47913 (robots/laptops)')
400
+ .option('--gateway <url>', 'InfiniBot gateway ws:// URL to surface the fleet in InfiniBot (scheduler)')
401
+ .option('--gateway-token <token>', 'auth token for the InfiniBot gateway')
402
+ .option('--token <token>', 'shared bearer token every device must present')
403
+ .option('--port <port>', 'mesh listen port (default 47913)')
404
+ .option('--tag <tag>', 'only discover peers carrying this tag (e.g. tag:robopark)')
405
+ .option('--architecture <text>', "one-line brief of this device's job in the wider system")
406
+ .option('--lan', 'force zero-config LAN auto-discovery (on by default for scheduler/robot)')
407
+ .option('--tailscale', 'force Tailscale peer discovery (on by default for scheduler/laptop)')
408
+ .option('--dashboard', 'force the web control UI (on by default for scheduler)')
409
+ .option('--start', 'launch the node now instead of just writing config')
410
+ .option('--yes', 'skip the guided wizard even with no role (non-interactive)')
411
+ .action(async (opts) => {
412
+ await roboparkSetup(config, opts);
413
+ });
414
+ cmd
415
+ .command('fleet')
416
+ .alias('status')
417
+ .description('Show every node the local (or a given) mesh node can see')
418
+ .option('--url <url>', 'node HTTP base to query (default the local node)')
419
+ .option('--token <token>', 'bearer token if the node requires auth')
420
+ .option('--watch', 'refresh the table every 2s until Ctrl-C')
421
+ .option('--json', 'print the raw status snapshot as JSON')
422
+ .action(async (opts) => {
423
+ await roboparkFleet(config, opts);
424
+ });
425
+ cmd
426
+ .command('run <node> <command...>')
427
+ .description('Run a slash-command on a node (use "." for the local node), e.g. robopark run panda-a /status')
428
+ .option('--url <url>', 'node HTTP base to send through (default the local node)')
429
+ .option('--token <token>', 'bearer token if the node requires auth')
430
+ .action(async (node, command, opts) => {
431
+ await roboparkRun(config, node, command, opts);
432
+ });
433
+ cmd
434
+ .command('apply')
435
+ .description('Publish this hub\'s shared config (providers, keys, policy) to every satellite')
436
+ .option('--url <url>', 'node HTTP base to apply through (default the local node)')
437
+ .option('--token <token>', 'bearer token if the node requires auth')
438
+ .action(async (opts) => {
439
+ await roboparkApply(config, opts);
440
+ });
441
+ cmd
442
+ .command('dashboard')
443
+ .description('Print (and optionally open) the web control dashboard URL')
444
+ .option('--url <url>', 'node HTTP base (default the local node)')
445
+ .option('--token <token>', 'bearer token if the node requires auth')
446
+ .option('--open', 'open the dashboard in the default browser')
447
+ .action(async (opts) => {
448
+ await roboparkDashboard(config, opts);
449
+ });
450
+ }
@@ -17,5 +17,10 @@ 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
+ tlsCert?: string;
24
+ tlsKey?: string;
20
25
  }
21
26
  export declare function serve(config: Conf<InfinicodeConfig>, opts: ServeOptions): Promise<void>;