infinicode 2.7.1 → 2.8.1

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.
@@ -0,0 +1,198 @@
1
+ /**
2
+ * RoboPark — `robopark setup`.
3
+ *
4
+ * One-liner setup per production machine. Each role turns on the right defaults
5
+ * and hides the mesh vocabulary.
6
+ *
7
+ * robopark setup --hub --name livekit-1 --site "Tel Aviv" --start --auto-start
8
+ * robopark setup --robot --name robobmw --start --auto-start
9
+ * robopark setup --control --start
10
+ */
11
+ import chalk from 'chalk';
12
+ import { mkdirSync, writeFileSync } from 'node:fs';
13
+ import { homedir } from 'node:os';
14
+ import { join } from 'node:path';
15
+ import { spawn } from 'node:child_process';
16
+ import { discoverContext, generateToken } from './discovery.js';
17
+ import { registerAutoStart } from './auto-start.js';
18
+ import { resolveInfinicodeBin } from './serve.js';
19
+ const DEFAULT_MESH_PORT = 47913;
20
+ const DEFAULT_SCHEDULER_PORT = 8080;
21
+ function saveToken(token) {
22
+ const dir = join(homedir(), '.robopark');
23
+ mkdirSync(dir, { recursive: true });
24
+ writeFileSync(join(dir, 'mesh.token'), token, 'utf8');
25
+ }
26
+ /** Run an external command and detach unless foreground is requested. */
27
+ function runDetached(cmd, args, env) {
28
+ const proc = spawn(cmd, args, {
29
+ stdio: 'ignore',
30
+ detached: true,
31
+ env: { ...process.env, ...env },
32
+ });
33
+ proc.on('error', (err) => {
34
+ console.log(chalk.red(` ✗ failed to start ${cmd}: ${err.message}`));
35
+ });
36
+ proc.unref();
37
+ }
38
+ /** Resolve the infinicode CLI and return the argv to spawn it. */
39
+ async function infinicodeArgv(args) {
40
+ const bin = await resolveInfinicodeBin();
41
+ if (bin)
42
+ return [bin.node, bin.script, ...args];
43
+ // Fallback: assume `infinicode` is on PATH. Will fail clearly if not.
44
+ return ['infinicode', ...args];
45
+ }
46
+ /** Build a Windows-task-friendly command string from an argv array. */
47
+ function commandString(argv) {
48
+ return argv.map(a => /[^a-zA-Z0-9_./:=,-]/.test(a) ? `"${a.replace(/"/g, '""')}"` : a).join(' ');
49
+ }
50
+ export async function roboparkSetup(config, opts) {
51
+ const ctx = await discoverContext();
52
+ const role = opts.role ?? 'control';
53
+ const token = opts.token ?? ctx.meshToken ?? generateToken();
54
+ const port = opts.port ? parseInt(opts.port, 10) : DEFAULT_MESH_PORT;
55
+ const name = opts.name ?? ctx.localName;
56
+ const gatewayHost = opts.gateway ?? (ctx.gateway ? `ws://${ctx.gateway.host}:${ctx.gateway.port ?? 18789}` : undefined);
57
+ const gatewayToken = opts.gatewayToken ?? ctx.gateway?.token;
58
+ saveToken(token);
59
+ console.log(chalk.bold('\n robopark setup'));
60
+ console.log(chalk.dim(' ' + '─'.repeat(52)));
61
+ console.log(` role: ${chalk.yellow(role)}`);
62
+ console.log(` name: ${chalk.cyan(name)}${opts.site ? chalk.dim(' @ ' + opts.site) : ''}`);
63
+ console.log(` mesh: ${chalk.cyan('0.0.0.0:' + port)}`);
64
+ console.log(` token: ${chalk.green(token)}`);
65
+ if (gatewayHost)
66
+ console.log(` gateway:${chalk.cyan(gatewayHost)}`);
67
+ console.log();
68
+ if (role === 'hub') {
69
+ await setupHub(config, opts, { name, token, port, gatewayHost, gatewayToken });
70
+ }
71
+ else if (role === 'robot') {
72
+ await setupRobot(config, opts, ctx, { name, token, port });
73
+ }
74
+ else {
75
+ await setupControl(config, opts, ctx, { name, token, port });
76
+ }
77
+ }
78
+ async function setupHub(config, opts, internal) {
79
+ const fed = {
80
+ ...(config.get('federation') ?? {}),
81
+ enabled: true,
82
+ role: 'hub',
83
+ port: internal.port,
84
+ displayName: internal.name,
85
+ token: internal.token,
86
+ lan: true,
87
+ };
88
+ config.set('federation', fed);
89
+ const infinicodeArgs = [
90
+ 'serve', '--hub',
91
+ '--name', internal.name,
92
+ '--port', String(internal.port),
93
+ '--token', internal.token,
94
+ '--lan',
95
+ '--auto-update',
96
+ '--supervised',
97
+ ];
98
+ if (internal.gatewayHost)
99
+ infinicodeArgs.push('--gateway', internal.gatewayHost);
100
+ if (internal.gatewayToken)
101
+ infinicodeArgs.push('--gateway-token', internal.gatewayToken);
102
+ const schedulerPort = opts.schedulerPort ? parseInt(opts.schedulerPort, 10) : DEFAULT_SCHEDULER_PORT;
103
+ console.log(chalk.dim(' hub commands:'));
104
+ console.log(' ' + chalk.cyan(`infinicode ${infinicodeArgs.join(' ')}`));
105
+ console.log(' ' + chalk.cyan(`robopark serve --port ${schedulerPort}`));
106
+ console.log();
107
+ if (opts.start) {
108
+ const hubArgv = await infinicodeArgv(infinicodeArgs);
109
+ console.log(chalk.dim(' starting infinicode hub…'));
110
+ runDetached(hubArgv[0], hubArgv.slice(1));
111
+ console.log(chalk.dim(' starting robopark scheduler…'));
112
+ runDetached(process.execPath, [process.argv[1], 'serve', '--port', String(schedulerPort)]);
113
+ }
114
+ if (opts.autoStart) {
115
+ const hubArgv = await infinicodeArgv(infinicodeArgs);
116
+ const reg = await registerAutoStart({
117
+ role: 'hub',
118
+ name: internal.name,
119
+ command: hubArgv[0],
120
+ args: hubArgv.slice(1),
121
+ });
122
+ console.log(reg.ok ? chalk.green(` ✓ ${reg.message}`) : chalk.yellow(` ⚠ ${reg.message}`));
123
+ const reg2 = await registerAutoStart({
124
+ role: 'hub-scheduler',
125
+ name: internal.name,
126
+ command: process.execPath,
127
+ args: [process.argv[1], 'serve', '--port', String(schedulerPort), '--foreground'],
128
+ });
129
+ console.log(reg2.ok ? chalk.green(` ✓ ${reg2.message}`) : chalk.yellow(` ⚠ ${reg2.message}`));
130
+ }
131
+ }
132
+ async function setupRobot(config, opts, ctx, internal) {
133
+ const hubUrl = opts.hub ?? (ctx.hub ? `http://${ctx.hub.ip}:${internal.port}` : undefined);
134
+ if (!hubUrl) {
135
+ console.log(chalk.red(' ✗ no hub discovered. Pass --hub http://HUB_IP:47913'));
136
+ return;
137
+ }
138
+ const fed = {
139
+ ...(config.get('federation') ?? {}),
140
+ enabled: true,
141
+ role: 'satellite',
142
+ port: internal.port,
143
+ displayName: internal.name,
144
+ token: internal.token,
145
+ seeds: [hubUrl],
146
+ lan: true,
147
+ };
148
+ config.set('federation', fed);
149
+ const args = [
150
+ 'serve', '--role', 'satellite',
151
+ '--name', internal.name,
152
+ '--port', String(internal.port),
153
+ '--token', internal.token,
154
+ '--seed', hubUrl,
155
+ '--lan',
156
+ '--auto-update',
157
+ '--supervised',
158
+ ];
159
+ console.log(chalk.dim(' robot command:'));
160
+ console.log(' ' + chalk.cyan(`infinicode ${args.join(' ')}`));
161
+ console.log();
162
+ if (opts.start) {
163
+ const robotArgv = await infinicodeArgv(args);
164
+ console.log(chalk.dim(' starting robot satellite node…'));
165
+ runDetached(robotArgv[0], robotArgv.slice(1));
166
+ }
167
+ if (opts.autoStart) {
168
+ const robotArgv = await infinicodeArgv(args);
169
+ const reg = await registerAutoStart({
170
+ role: 'robot',
171
+ name: internal.name,
172
+ command: robotArgv[0],
173
+ args: robotArgv.slice(1),
174
+ });
175
+ console.log(reg.ok ? chalk.green(` ✓ ${reg.message}`) : chalk.yellow(` ⚠ ${reg.message}`));
176
+ }
177
+ }
178
+ async function setupControl(config, opts, ctx, internal) {
179
+ const hubUrl = opts.hub ?? (ctx.hub ? `http://${ctx.hub.ip}:${internal.port}` : undefined);
180
+ const args = [
181
+ 'mesh', 'install',
182
+ '--token', internal.token,
183
+ '--name', internal.name,
184
+ hubUrl ? '--seed' : '', hubUrl ?? '',
185
+ ].filter(Boolean);
186
+ console.log(chalk.dim(' control command:'));
187
+ console.log(' ' + chalk.cyan(`infinicode ${args.join(' ')}`));
188
+ console.log();
189
+ if (opts.start) {
190
+ const controlArgv = await infinicodeArgv(args);
191
+ console.log(chalk.dim(' registering infinicode MCP host…'));
192
+ const proc = spawn(controlArgv[0], controlArgv.slice(1), { stdio: 'inherit' });
193
+ proc.on('error', (err) => {
194
+ console.log(chalk.red(` ✗ failed to start infinicode: ${err.message}`));
195
+ });
196
+ await new Promise(resolve => proc.on('close', () => resolve()));
197
+ }
198
+ }
@@ -1,2 +1 @@
1
- #!/usr/bin/env node
2
1
  export {};
@@ -1,23 +1,92 @@
1
- #!/usr/bin/env node
2
1
  /**
3
- * `robopark` standalone entry for the RoboPark fleet control CLI.
2
+ * RoboPark CLI entry point (shipped as `infinicode/robopark` and `robopark`).
4
3
  *
5
- * Same verbs as `infinicode robopark …`, exposed as a top-level `robopark`
6
- * command so operators type `robopark fleet` / `robopark setup` directly.
4
+ * A thin operator layer over the infinicode mesh. Keeps product concerns
5
+ * (scheduler, fleet UI, video sessions, InfiniBot map) separate from the
6
+ * execution kernel.
7
7
  */
8
8
  import { Command } from 'commander';
9
+ import chalk from 'chalk';
9
10
  import Conf from 'conf';
10
- import { attachRoboparkSubcommands } from './commands/robopark.js';
11
- import { DEFAULT_CONFIG } from './kernel/config-schema.js';
12
- const VERSION = '2.5.1';
11
+ import { attachRoboparkSubcommands as attachLegacy } from './commands/robopark.js';
12
+ import { roboparkScan } from './robopark/scan.js';
13
+ import { roboparkServe } from './robopark/serve.js';
14
+ import { roboparkSetup } from './robopark/setup.js';
13
15
  const config = new Conf({
14
- projectName: 'infinicode', // share infinicode's config store (providers, federation)
15
- defaults: DEFAULT_CONFIG,
16
+ projectName: 'infinicode',
16
17
  });
17
- const program = new Command();
18
+ const program = new Command('robopark')
19
+ .description('RoboPark fleet control CLI — set up, watch, and drive talking robots')
20
+ .version('2.8.1');
18
21
  program
19
- .name('robopark')
20
- .description('RoboPark fleet control setup, fleet status, run, apply, dashboard (rides the infinicode mesh)')
21
- .version(VERSION);
22
- attachRoboparkSubcommands(program, config);
23
- program.parse();
22
+ .command('scan')
23
+ .description('Auto-discover the fleet and print ready-to-run setup commands')
24
+ .option('--token <token>', 'mesh auth token to use')
25
+ .option('--output <dir>', 'write install scripts to this directory')
26
+ .option('--no-gateway', 'skip InfiniBot gateway in generated commands')
27
+ .action(async (opts) => {
28
+ await roboparkScan({
29
+ token: opts.token,
30
+ output: opts.output,
31
+ gateway: opts.gateway,
32
+ });
33
+ });
34
+ program
35
+ .command('serve')
36
+ .description('Start the RoboPark scheduler + web UI')
37
+ .option('--port <port>', 'scheduler HTTP port', '8080')
38
+ .option('--host <host>', 'scheduler bind host', '0.0.0.0')
39
+ .option('--data-dir <dir>', 'scheduler data directory')
40
+ .option('--gateway <url>', 'InfiniBot gateway ws:// URL')
41
+ .option('--gateway-token <token>', 'InfiniBot gateway token')
42
+ .option('--gateway-password <pw>', 'InfiniBot gateway password')
43
+ .option('--foreground', 'stay in foreground; do not detach')
44
+ .action(async (opts) => {
45
+ await roboparkServe(opts);
46
+ });
47
+ program
48
+ .command('setup')
49
+ .description('Set up this device in one pass')
50
+ .option('--hub', 'this device is the on-site hub')
51
+ .option('--robot', 'this device is a robot')
52
+ .option('--control', 'this device is a control station')
53
+ .option('--role <role>', 'explicit role: hub | robot | control')
54
+ .option('--name <name>', 'device name (defaults to hostname)')
55
+ .option('--site <site>', 'physical location, e.g. "Tel Aviv"')
56
+ .option('--hub-url <url>', 'hub base URL for robots/control')
57
+ .option('--token <token>', 'shared mesh token')
58
+ .option('--port <port>', 'mesh listen port', '47913')
59
+ .option('--gateway <url>', 'InfiniBot gateway ws:// URL')
60
+ .option('--gateway-token <token>', 'InfiniBot gateway token')
61
+ .option('--scheduler-port <port>', 'scheduler port on hub', '8080')
62
+ .option('--tailscale-auth <token>', 'Tailscale auth key for the hub')
63
+ .option('--tag <tag>', 'Tailscale tag filter')
64
+ .option('--start', 'launch the node now')
65
+ .option('--auto-start', 'register to start on boot')
66
+ .option('--yes', 'non-interactive mode')
67
+ .action(async (opts) => {
68
+ const role = opts.role ?? (opts.hub ? 'hub' : opts.robot ? 'robot' : opts.control ? 'control' : undefined);
69
+ if (!role) {
70
+ console.log(chalk.red(' pass a role: --hub | --robot | --control'));
71
+ process.exit(1);
72
+ }
73
+ await roboparkSetup(config, {
74
+ role,
75
+ name: opts.name,
76
+ site: opts.site,
77
+ hub: opts.hubUrl,
78
+ token: opts.token,
79
+ port: opts.port,
80
+ gateway: opts.gateway,
81
+ gatewayToken: opts.gatewayToken,
82
+ schedulerPort: opts.schedulerPort,
83
+ tailscaleAuth: opts.tailscaleAuth,
84
+ tag: opts.tag,
85
+ start: opts.start,
86
+ autoStart: opts.autoStart,
87
+ yes: opts.yes,
88
+ });
89
+ });
90
+ // Keep legacy fleet/run/apply/dashboard commands available while migrating.
91
+ attachLegacy(program, config, { skipSetup: true });
92
+ await program.parseAsync(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "infinicode",
3
- "version": "2.7.1",
3
+ "version": "2.8.1",
4
4
  "description": "OpenKernel — provider-agnostic AI execution kernel. Native coding agent + mission-driven execution runtime.",
5
5
  "type": "module",
6
6
  "main": "./dist/kernel/index.js",