infinicode 2.8.113 → 2.8.115
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 +20 -2
- package/dist/cli.js +23 -0
- package/dist/commands/mcp.d.ts +2 -0
- package/dist/commands/mcp.js +15 -4
- package/dist/commands/mesh-install.d.ts +1 -0
- package/dist/commands/mesh-install.js +28 -11
- package/dist/commands/mesh-link.d.ts +10 -0
- package/dist/commands/mesh-link.js +46 -0
- package/dist/kernel/federation/dashboard-html.d.ts +1 -1
- package/dist/kernel/federation/dashboard-html.js +5 -5
- package/dist/kernel/federation/index.d.ts +2 -0
- package/dist/kernel/federation/index.js +1 -0
- package/dist/kernel/federation/mesh-link.d.ts +16 -0
- package/dist/kernel/federation/mesh-link.js +52 -0
- package/dist/kernel/federation/transport-http.js +24 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -130,7 +130,25 @@ infinicode serve --hub --gateway ws://<infinibot-host>:18789 --gateway-token <to
|
|
|
130
130
|
|
|
131
131
|
Discovery options: `--lan` (same subnet, zero-config UDP broadcast, port 47915), `--tailscale` (LAN+remote), `--seed <url>` (exact) — they combine. Full guide: `docs/user-manual.html`; design deep-dive: `docs/federation-architecture.html`.
|
|
132
132
|
|
|
133
|
-
|
|
133
|
+
### One-link MCP mesh connection
|
|
134
|
+
|
|
135
|
+
On the first Tailscale device, generate the direct link and start MCP. Infinicode creates and persists the shared token automatically:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
infinicode mesh link
|
|
139
|
+
infinicode mcp --tailscale
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Give the single generated `http://.../fed/join?token=...` URL to the agent on the second device. It can connect immediately with either:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
infinicode mcp --connect "PASTE_LINK_HERE"
|
|
146
|
+
infinicode mesh join "PASTE_LINK_HERE" --host opencode
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
The first form runs an MCP instance directly. The second persists the same MCP connection in OpenCode or Claude (`--host all` configures both). The link is a credential: share it only inside the tailnet and rotate the mesh token if it leaks.
|
|
150
|
+
|
|
151
|
+
> The Windows TUI binaries are not shipped in the npm package (they're large and platform-specific), so `infinicode run` needs a local TUI build; the mesh commands (`serve`, `mcp`, `mission`, `console`) run everywhere as pure Node.
|
|
134
152
|
|
|
135
153
|
### As a library
|
|
136
154
|
|
|
@@ -621,4 +639,4 @@ MIT
|
|
|
621
639
|
|
|
622
640
|
---
|
|
623
641
|
|
|
624
|
-
⚡ Built for sovereign computing. Your code, your models, your hardware.
|
|
642
|
+
⚡ Built for sovereign computing. Your code, your models, your hardware.
|
package/dist/cli.js
CHANGED
|
@@ -16,6 +16,7 @@ import { serve } from './commands/serve.js';
|
|
|
16
16
|
import { mcpCommand } from './commands/mcp.js';
|
|
17
17
|
import { maintain } from './commands/maintain.js';
|
|
18
18
|
import { meshInstall, meshUninstall } from './commands/mesh-install.js';
|
|
19
|
+
import { meshLink } from './commands/mesh-link.js';
|
|
19
20
|
import { installTui } from './commands/install-tui.js';
|
|
20
21
|
import { attachRoboparkSubcommands } from './commands/robopark.js';
|
|
21
22
|
import { DEFAULT_CONFIG } from './kernel/config-schema.js';
|
|
@@ -351,12 +352,33 @@ meshCmd
|
|
|
351
352
|
.option('--host <host>', 'target host: claude | opencode | all', 'claude')
|
|
352
353
|
.option('--token <token>', 'shared bearer token every device must present (recommended off-LAN)')
|
|
353
354
|
.option('--name <name>', 'friendly node name for this device')
|
|
355
|
+
.option('--link <url>', 'direct mesh join URL generated on the other device')
|
|
354
356
|
.option('--tailscale', 'discover peers over Tailscale instead of / as well as the LAN')
|
|
355
357
|
.option('--no-lan', 'disable zero-config LAN auto-discovery')
|
|
356
358
|
.option('--print', 'dry run — show what would be written without changing anything')
|
|
357
359
|
.action(async (opts) => {
|
|
358
360
|
await meshInstall(opts);
|
|
359
361
|
});
|
|
362
|
+
meshCmd
|
|
363
|
+
.command('link')
|
|
364
|
+
.description('Generate one direct Tailscale URL another MCP agent can use to join this mesh')
|
|
365
|
+
.option('--host <host>', 'Tailscale MagicDNS name or 100.x IP (auto-detected by default)')
|
|
366
|
+
.option('--port <port>', 'MCP mesh port on this device (default: serve port + 1)')
|
|
367
|
+
.option('--token <token>', 'shared mesh token (defaults to saved federation token)')
|
|
368
|
+
.option('--https', 'generate an https:// link for a TLS-enabled mesh node')
|
|
369
|
+
.option('--quiet', 'print only the URL')
|
|
370
|
+
.action(async (opts) => {
|
|
371
|
+
await meshLink(config, opts);
|
|
372
|
+
});
|
|
373
|
+
meshCmd
|
|
374
|
+
.command('join <link>')
|
|
375
|
+
.description('Install a direct mesh URL into Claude Code and/or OpenCode')
|
|
376
|
+
.option('--host <host>', 'target host: claude | opencode | all', 'all')
|
|
377
|
+
.option('--name <name>', 'friendly node name for this device')
|
|
378
|
+
.option('--print', 'dry run - show what would be written')
|
|
379
|
+
.action(async (link, opts) => {
|
|
380
|
+
await meshInstall({ ...opts, link, lan: false });
|
|
381
|
+
});
|
|
360
382
|
meshCmd
|
|
361
383
|
.command('uninstall')
|
|
362
384
|
.description('Remove the infinicode MCP server from your coding host')
|
|
@@ -387,6 +409,7 @@ program
|
|
|
387
409
|
.option('--lan', 'auto-discover + connect peers on the same LAN via UDP broadcast (no seed/Tailscale needed)')
|
|
388
410
|
.option('--lan-port <port>', 'UDP discovery port for --lan (default 47915)')
|
|
389
411
|
.option('--tag <tag>', 'only discover peers carrying this tag (Tailscale ACL tag or LAN beacon tag)')
|
|
412
|
+
.option('--connect <url>', 'single direct mesh join URL generated by `infinicode mesh link`')
|
|
390
413
|
.action(async (opts) => {
|
|
391
414
|
await mcpCommand(config, opts);
|
|
392
415
|
});
|
package/dist/commands/mcp.d.ts
CHANGED
|
@@ -10,5 +10,7 @@ export interface McpOptions {
|
|
|
10
10
|
tag?: string;
|
|
11
11
|
lan?: boolean;
|
|
12
12
|
lanPort?: string;
|
|
13
|
+
/** Canonical URL generated by `infinicode mesh link`. */
|
|
14
|
+
connect?: string;
|
|
13
15
|
}
|
|
14
16
|
export declare function mcpCommand(config: Conf<InfinicodeConfig>, opts: McpOptions): Promise<void>;
|
package/dist/commands/mcp.js
CHANGED
|
@@ -14,6 +14,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
14
14
|
import { dirname, join } from 'node:path';
|
|
15
15
|
import { buildKernelFromConfig, registerCloudProviders } from '../kernel/setup.js';
|
|
16
16
|
import { Federation, GoalLoop, loadOrCreateIdentity, serveMcpStdio, SilentLogger, } from '../kernel/index.js';
|
|
17
|
+
import { parseMeshJoinLink, redactMeshJoinLink } from '../kernel/federation/mesh-link.js';
|
|
17
18
|
function pkgVersion() {
|
|
18
19
|
try {
|
|
19
20
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
@@ -32,10 +33,14 @@ const stderrLogger = {
|
|
|
32
33
|
};
|
|
33
34
|
export async function mcpCommand(config, opts) {
|
|
34
35
|
const fed = config.get('federation') ?? {};
|
|
36
|
+
const direct = opts.connect ? parseMeshJoinLink(opts.connect) : undefined;
|
|
35
37
|
const role = (opts.role ?? fed.role ?? 'peer');
|
|
36
38
|
// Default to 47914 so a co-located `infinicode serve` (47913) doesn't clash.
|
|
37
39
|
const port = opts.meshPort ? parseInt(opts.meshPort, 10) : (fed.port ?? 47913) + 1;
|
|
38
|
-
|
|
40
|
+
if (opts.token && direct?.token && opts.token !== direct.token) {
|
|
41
|
+
throw new Error('the explicit --token does not match the direct mesh link token');
|
|
42
|
+
}
|
|
43
|
+
const token = opts.token ?? direct?.token ?? fed.token;
|
|
39
44
|
const version = pkgVersion();
|
|
40
45
|
const kernel = buildKernelFromConfig(config, { logger: new SilentLogger() });
|
|
41
46
|
const identity = loadOrCreateIdentity({ role, displayName: opts.name ?? fed.displayName });
|
|
@@ -94,9 +99,15 @@ export async function mcpCommand(config, opts) {
|
|
|
94
99
|
// Continue serving MCP tools; dispatch will report "no peers" until fixed.
|
|
95
100
|
}
|
|
96
101
|
// Join the mesh: static seeds + optional Tailscale discovery.
|
|
97
|
-
const seeds = [...(fed.seeds ?? []), ...(opts.seed ?? [])];
|
|
98
|
-
for (const url of seeds)
|
|
99
|
-
await federation.connect(url).catch(() => null);
|
|
102
|
+
const seeds = [...(fed.seeds ?? []), ...(opts.seed ?? []), ...(direct ? [direct.seed] : [])];
|
|
103
|
+
for (const url of [...new Set(seeds)]) {
|
|
104
|
+
const peer = await federation.connect(url).catch(() => null);
|
|
105
|
+
if (direct && url === direct.seed) {
|
|
106
|
+
if (!peer)
|
|
107
|
+
throw new Error(`direct mesh connection failed: ${redactMeshJoinLink(direct.link)}`);
|
|
108
|
+
stderrLogger.info(`direct mesh connected to ${peer.displayName} @ ${url}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
100
111
|
if (opts.tailscale) {
|
|
101
112
|
const n = await federation.discoverAndConnect(opts.tag).catch(() => 0);
|
|
102
113
|
stderrLogger.info(`tailscale discovery connected ${n} peer(s)`);
|
|
@@ -17,6 +17,7 @@ import { homedir } from 'node:os';
|
|
|
17
17
|
import { join, dirname } from 'node:path';
|
|
18
18
|
import { fileURLToPath } from 'node:url';
|
|
19
19
|
import { findExecutable } from '../kernel/agents/backends/detect.js';
|
|
20
|
+
import { parseMeshJoinLink, redactMeshJoinLink } from '../kernel/federation/mesh-link.js';
|
|
20
21
|
/** How a host should spawn the infinicode MCP node. Resolves a global binary
|
|
21
22
|
* if present, else falls back to `node <this package's cli.js>`. */
|
|
22
23
|
function resolveLaunch() {
|
|
@@ -29,12 +30,18 @@ function resolveLaunch() {
|
|
|
29
30
|
/** The `infinicode mcp …` argv the host should run. */
|
|
30
31
|
function meshArgs(opts) {
|
|
31
32
|
const args = ['mcp'];
|
|
32
|
-
if (opts.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
if (opts.link) {
|
|
34
|
+
parseMeshJoinLink(opts.link);
|
|
35
|
+
args.push('--connect', opts.link);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
if (opts.lan !== false)
|
|
39
|
+
args.push('--lan');
|
|
40
|
+
if (opts.tailscale)
|
|
41
|
+
args.push('--tailscale');
|
|
42
|
+
if (opts.token)
|
|
43
|
+
args.push('--token', opts.token);
|
|
44
|
+
}
|
|
38
45
|
if (opts.name)
|
|
39
46
|
args.push('--name', opts.name);
|
|
40
47
|
return args;
|
|
@@ -114,6 +121,7 @@ function selectedHosts(host) {
|
|
|
114
121
|
return ['claude'];
|
|
115
122
|
}
|
|
116
123
|
export async function meshInstall(opts) {
|
|
124
|
+
const direct = opts.link ? parseMeshJoinLink(opts.link) : undefined;
|
|
117
125
|
const hosts = selectedHosts(opts.host);
|
|
118
126
|
console.log(chalk.bold('\n🕸 infinicode mesh — MCP registration\n'));
|
|
119
127
|
for (const host of hosts) {
|
|
@@ -121,7 +129,10 @@ export async function meshInstall(opts) {
|
|
|
121
129
|
const label = host === 'claude' ? 'Claude Code' : 'OpenCode';
|
|
122
130
|
const verb = opts.print ? 'would write' : res.existed ? 'updated' : 'added';
|
|
123
131
|
console.log(` ${chalk.green('✓')} ${chalk.cyan(label)} — ${verb} ${chalk.dim(res.path)}`);
|
|
124
|
-
|
|
132
|
+
const shown = opts.link
|
|
133
|
+
? JSON.stringify(res.entry).split(opts.link).join(redactMeshJoinLink(opts.link))
|
|
134
|
+
: JSON.stringify(res.entry);
|
|
135
|
+
console.log(chalk.dim(` ${shown}`));
|
|
125
136
|
}
|
|
126
137
|
// Warn if the target CLI backend isn't installed on THIS machine — a worker
|
|
127
138
|
// node can only run agents for CLIs it actually has.
|
|
@@ -138,10 +149,16 @@ export async function meshInstall(opts) {
|
|
|
138
149
|
return;
|
|
139
150
|
}
|
|
140
151
|
console.log(chalk.bold('\n Next:'));
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
152
|
+
if (direct) {
|
|
153
|
+
console.log(` 1. Restart the coding host on this device.`);
|
|
154
|
+
console.log(` 2. Ask the agent to call ${chalk.italic('"list_nodes"')}.`);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
console.log(` 1. Run the SAME command on the other laptop.`);
|
|
158
|
+
console.log(` 2. Restart your coding host (Claude Code) on both.`);
|
|
159
|
+
console.log(` 3. Ask it: ${chalk.italic('"list_nodes"')} then ${chalk.italic('"spawn_agent on <laptop> with agent claude to …"')}`);
|
|
160
|
+
}
|
|
161
|
+
if (!opts.token && !direct?.token) {
|
|
145
162
|
console.log(chalk.dim(`\n Tip: on an untrusted network, re-run with --token <secret> on every device.`));
|
|
146
163
|
}
|
|
147
164
|
console.log();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type Conf from 'conf';
|
|
2
|
+
import type { InfinicodeConfig } from '../kernel/config-schema.js';
|
|
3
|
+
export interface MeshLinkOptions {
|
|
4
|
+
host?: string;
|
|
5
|
+
port?: string;
|
|
6
|
+
token?: string;
|
|
7
|
+
https?: boolean;
|
|
8
|
+
quiet?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function meshLink(config: Conf<InfinicodeConfig>, opts: MeshLinkOptions): Promise<string>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { execa } from 'execa';
|
|
3
|
+
import { randomBytes } from 'node:crypto';
|
|
4
|
+
import { buildMeshJoinLink } from '../kernel/federation/mesh-link.js';
|
|
5
|
+
async function tailscaleHost() {
|
|
6
|
+
try {
|
|
7
|
+
const { stdout } = await execa('tailscale', ['status', '--json'], { timeout: 5000 });
|
|
8
|
+
const self = JSON.parse(stdout).Self;
|
|
9
|
+
const dns = self?.DNSName?.replace(/\.$/, '');
|
|
10
|
+
if (dns)
|
|
11
|
+
return dns;
|
|
12
|
+
return self?.TailscaleIPs?.find(ip => ip.includes('.'));
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export async function meshLink(config, opts) {
|
|
19
|
+
const fed = config.get('federation') ?? {};
|
|
20
|
+
const host = opts.host?.trim() || await tailscaleHost();
|
|
21
|
+
if (!host)
|
|
22
|
+
throw new Error('Tailscale address unavailable; run tailscale up or pass --host <MagicDNS-name-or-100.x-IP>');
|
|
23
|
+
const rawPort = opts.port ?? String((fed.port ?? 47913) + 1);
|
|
24
|
+
const port = Number.parseInt(rawPort, 10);
|
|
25
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
26
|
+
throw new Error('mesh link port must be between 1 and 65535');
|
|
27
|
+
const generatedToken = !opts.token && !fed.token;
|
|
28
|
+
const token = opts.token ?? fed.token ?? randomBytes(24).toString('base64url');
|
|
29
|
+
if (token !== fed.token)
|
|
30
|
+
config.set('federation', { ...fed, token });
|
|
31
|
+
const bracketedHost = host.includes(':') && !host.startsWith('[') ? `[${host}]` : host;
|
|
32
|
+
const link = buildMeshJoinLink(`${opts.https ? 'https' : 'http'}://${bracketedHost}:${port}`, token);
|
|
33
|
+
if (opts.quiet) {
|
|
34
|
+
console.log(link);
|
|
35
|
+
return link;
|
|
36
|
+
}
|
|
37
|
+
console.log(chalk.bold('\n Infinicode direct mesh link'));
|
|
38
|
+
console.log(chalk.cyan(` ${link}`));
|
|
39
|
+
console.log(chalk.green(`\n ${generatedToken ? 'Generated and saved a new mesh token.' : 'Mesh token saved and ready.'}`));
|
|
40
|
+
console.log(chalk.dim('\n Give this one URL to the agent on the other device.'));
|
|
41
|
+
console.log(` Direct MCP: ${chalk.cyan(`infinicode mcp --connect "${link}"`)}`);
|
|
42
|
+
console.log(` OpenCode/Claude: ${chalk.cyan(`infinicode mesh join "${link}" --host all`)}`);
|
|
43
|
+
console.log(chalk.yellow('\n This link grants mesh access. Treat it like a password and rotate the token if shared accidentally.'));
|
|
44
|
+
console.log();
|
|
45
|
+
return link;
|
|
46
|
+
}
|