talon-agent 5.21.0 → 5.22.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/package.json +2 -2
- package/src/core/mesh/devices/service.ts +6 -2
- package/src/core/mesh/links/bridge-links.ts +80 -2
- package/src/core/mesh/links/node-provision.ts +114 -5
- package/src/core/tools/ops/mesh.ts +12 -4
- package/src/frontend/native/bridge/routes/host.ts +12 -2
- package/src/frontend/native/bridge/routes/pre-auth.ts +8 -2
- package/src/frontend/native/surface/handlers.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "talon-agent",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.22.0",
|
|
4
4
|
"description": "Multi-frontend AI agent with full tool access, streaming, cron jobs, and plugin system",
|
|
5
5
|
"author": "The Falconry",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
"build:fusefs": "node native/talon-fusefs/build.mjs"
|
|
102
102
|
},
|
|
103
103
|
"dependencies": {
|
|
104
|
-
"@anthropic-ai/claude-agent-sdk": "^0.3.
|
|
104
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.284",
|
|
105
105
|
"@anthropic-ai/sdk": "^0.104.1",
|
|
106
106
|
"@brave/brave-search-mcp-server": "^2.0.75",
|
|
107
107
|
"@clack/prompts": "^1.2.0",
|
|
@@ -877,8 +877,12 @@ export class MeshService {
|
|
|
877
877
|
}
|
|
878
878
|
|
|
879
879
|
/** GET /node/install — serve a grant's installer script (single-use). */
|
|
880
|
-
openNodeInstall(
|
|
881
|
-
|
|
880
|
+
openNodeInstall(
|
|
881
|
+
token: string,
|
|
882
|
+
os?: string | null,
|
|
883
|
+
arch?: string | null,
|
|
884
|
+
): Promise<{ script: string; filename: string } | null> {
|
|
885
|
+
return this.links.openNodeInstall(token, os, arch);
|
|
882
886
|
}
|
|
883
887
|
|
|
884
888
|
/** GET /node/binary — serve a grant's binary (single-use). */
|
|
@@ -24,8 +24,10 @@ import {
|
|
|
24
24
|
type NodeBinaryResolver,
|
|
25
25
|
} from "./node-binaries.js";
|
|
26
26
|
import {
|
|
27
|
+
autoInstallOneLiners,
|
|
27
28
|
checkBridgeUrl,
|
|
28
29
|
installOneLiner,
|
|
30
|
+
installRefusalScript,
|
|
29
31
|
NodeProvisionStore,
|
|
30
32
|
} from "./node-provision.js";
|
|
31
33
|
import {
|
|
@@ -141,9 +143,10 @@ export class BridgeLinks {
|
|
|
141
143
|
name?: unknown,
|
|
142
144
|
bridgeUrl?: unknown,
|
|
143
145
|
): Promise<MeshToolResult> {
|
|
146
|
+
const auto = isBlank(os) && isBlank(arch);
|
|
144
147
|
const goos = normalizeGoos(os);
|
|
145
148
|
const goarch = normalizeGoarch(arch);
|
|
146
|
-
if (!goos || !goarch) {
|
|
149
|
+
if (!auto && (!goos || !goarch)) {
|
|
147
150
|
return { ok: false, text: unknownTargetText(os, arch) };
|
|
148
151
|
}
|
|
149
152
|
const info = this.bridgeInfo;
|
|
@@ -161,6 +164,33 @@ export class BridgeLinks {
|
|
|
161
164
|
}
|
|
162
165
|
const base = this.bridgeBaseUrl(info, bridgeUrl);
|
|
163
166
|
if (typeof base !== "string") return { ok: false, text: base.error };
|
|
167
|
+
if (auto || !goos || !goarch) {
|
|
168
|
+
const pending = this.provision.createAuto({
|
|
169
|
+
...(typeof name === "string" && name.trim()
|
|
170
|
+
? { name: name.trim() }
|
|
171
|
+
: {}),
|
|
172
|
+
bridgeUrl: base,
|
|
173
|
+
bearerToken: this.linkCredential(info.token, NODE_SCOPES, "install"),
|
|
174
|
+
...(info.fingerprint ? { fingerprint: info.fingerprint } : {}),
|
|
175
|
+
...(info.spkiPin ? { spkiPin: info.spkiPin } : {}),
|
|
176
|
+
});
|
|
177
|
+
const cmd = autoInstallOneLiners(pending);
|
|
178
|
+
return {
|
|
179
|
+
ok: true,
|
|
180
|
+
text: [
|
|
181
|
+
"Run ONE of these on the new host — it reports its own OS and CPU, and the bridge picks the matching talon-node:",
|
|
182
|
+
"",
|
|
183
|
+
"Linux / macOS:",
|
|
184
|
+
` ${cmd.posix}`,
|
|
185
|
+
"",
|
|
186
|
+
"Windows (elevated PowerShell or cmd):",
|
|
187
|
+
` ${cmd.windows}`,
|
|
188
|
+
"",
|
|
189
|
+
`It installs talon-node (sha256-verified), pins the bridge certificate, and registers a boot service — the host appears on the mesh within a minute. Supported: ${NODE_TARGETS.map((t) => `${t.goos}/${t.goarch}`).join(", ")}.`,
|
|
190
|
+
`Single-use link, expires in 30 minutes. The host must be able to reach ${base}.`,
|
|
191
|
+
].join("\n"),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
164
194
|
let bin;
|
|
165
195
|
try {
|
|
166
196
|
bin = await this.resolveNode(goos, goarch);
|
|
@@ -305,7 +335,46 @@ export class BridgeLinks {
|
|
|
305
335
|
}
|
|
306
336
|
|
|
307
337
|
/** GET /node/install — serve a grant's installer script (single-use). */
|
|
308
|
-
|
|
338
|
+
/**
|
|
339
|
+
* An auto grant is pinned here first, from the os/arch its one-liner
|
|
340
|
+
* reported; a host with no build gets a failing script and the grant
|
|
341
|
+
* stays unspent.
|
|
342
|
+
*/
|
|
343
|
+
async openNodeInstall(
|
|
344
|
+
token: string,
|
|
345
|
+
os?: string | null,
|
|
346
|
+
arch?: string | null,
|
|
347
|
+
): Promise<{ script: string; filename: string } | null> {
|
|
348
|
+
if (this.provision.isPending(token)) {
|
|
349
|
+
const goos = normalizeGoos(os);
|
|
350
|
+
const goarch = normalizeGoarch(arch);
|
|
351
|
+
if (!goos || !goarch) {
|
|
352
|
+
return {
|
|
353
|
+
script: installRefusalScript(
|
|
354
|
+
`no talon-node build for os=${os ?? ""} arch=${arch ?? ""}`,
|
|
355
|
+
),
|
|
356
|
+
filename: "install-talon-node.txt",
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
try {
|
|
360
|
+
const bin = await this.resolveNode(goos, goarch);
|
|
361
|
+
this.provision.pin(token, {
|
|
362
|
+
goos,
|
|
363
|
+
goarch,
|
|
364
|
+
binaryPath: bin.path,
|
|
365
|
+
sha256: bin.sha256,
|
|
366
|
+
size: bin.size,
|
|
367
|
+
version: bin.version,
|
|
368
|
+
});
|
|
369
|
+
} catch (err) {
|
|
370
|
+
return {
|
|
371
|
+
script: installRefusalScript(
|
|
372
|
+
`could not resolve talon-node for ${goos}/${goarch}: ${(err as Error).message}`,
|
|
373
|
+
),
|
|
374
|
+
filename: "install-talon-node.txt",
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
}
|
|
309
378
|
return this.provision.openScript(token);
|
|
310
379
|
}
|
|
311
380
|
|
|
@@ -355,6 +424,15 @@ export class BridgeLinks {
|
|
|
355
424
|
}
|
|
356
425
|
}
|
|
357
426
|
|
|
427
|
+
/** An omitted tool argument: absent, or an empty/whitespace string. */
|
|
428
|
+
function isBlank(value: unknown): boolean {
|
|
429
|
+
return (
|
|
430
|
+
value === undefined ||
|
|
431
|
+
value === null ||
|
|
432
|
+
(typeof value === "string" && !value.trim())
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
|
|
358
436
|
/** Error text for an os/arch pair outside the talon-node build matrix. */
|
|
359
437
|
function unknownTargetText(os: unknown, arch: unknown): string {
|
|
360
438
|
return `No talon-node target for os="${String(os)}", arch="${String(arch)}". Supported: ${NODE_TARGETS.map(
|
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
* GET /node/install?provision=<token> → installer script (once)
|
|
10
10
|
* GET /node/binary?provision=<token> → the binary itself (once)
|
|
11
11
|
*
|
|
12
|
+
* A grant can also be minted WITHOUT a target ("auto"): the one-liner then
|
|
13
|
+
* reports the host's own `uname -s`/`uname -m` (or PowerShell's
|
|
14
|
+
* PROCESSOR_ARCHITECTURE) on the script fetch, and the caller resolves the
|
|
15
|
+
* binary and pins it into the grant before the script is rendered — so the
|
|
16
|
+
* script still embeds the digest and nothing about integrity changes.
|
|
17
|
+
*
|
|
12
18
|
* The routes must work pre-auth — the whole point is a host that holds no
|
|
13
19
|
* bridge credential yet — so the grant token IS the authorization, exactly
|
|
14
20
|
* like streamed-transfer tokens (transfers/transfers.ts): random 192-bit, single-use
|
|
@@ -56,8 +62,22 @@ export type NodeProvisionGrant = {
|
|
|
56
62
|
binaryUsed: boolean;
|
|
57
63
|
};
|
|
58
64
|
|
|
65
|
+
/** The fields an auto grant is missing until the target host reports in. */
|
|
66
|
+
export type NodeProvisionTarget = Pick<
|
|
67
|
+
NodeProvisionGrant,
|
|
68
|
+
"goos" | "goarch" | "binaryPath" | "sha256" | "size" | "version"
|
|
69
|
+
>;
|
|
70
|
+
|
|
71
|
+
/** An auto grant before its target is known — everything but the binary. */
|
|
72
|
+
export type PendingNodeProvision = Omit<
|
|
73
|
+
NodeProvisionGrant,
|
|
74
|
+
keyof NodeProvisionTarget | "scriptUsed" | "binaryUsed"
|
|
75
|
+
>;
|
|
76
|
+
|
|
59
77
|
export class NodeProvisionStore {
|
|
60
78
|
private readonly grants = new Map<string, NodeProvisionGrant>();
|
|
79
|
+
/** Auto grants whose target host hasn't fetched the script yet. */
|
|
80
|
+
private readonly pending = new Map<string, PendingNodeProvision>();
|
|
61
81
|
|
|
62
82
|
constructor(private readonly ttlMs = GRANT_TTL_MS) {}
|
|
63
83
|
|
|
@@ -70,11 +90,7 @@ export class NodeProvisionStore {
|
|
|
70
90
|
this.sweep();
|
|
71
91
|
const full: NodeProvisionGrant = {
|
|
72
92
|
...grant,
|
|
73
|
-
|
|
74
|
-
// keep them to characters that can't break out of a quoted string.
|
|
75
|
-
...(grant.name
|
|
76
|
-
? { name: grant.name.replace(/[^\w .-]+/g, "").slice(0, 64) }
|
|
77
|
-
: {}),
|
|
93
|
+
...sanitizedName(grant.name),
|
|
78
94
|
token: randomBytes(24).toString("base64url"),
|
|
79
95
|
createdAt: Date.now(),
|
|
80
96
|
scriptUsed: false,
|
|
@@ -84,6 +100,48 @@ export class NodeProvisionStore {
|
|
|
84
100
|
return full;
|
|
85
101
|
}
|
|
86
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Mint an auto grant: no target yet. {@link pin} fills it in when the host
|
|
105
|
+
* fetches the script and reports its own os/arch.
|
|
106
|
+
*/
|
|
107
|
+
createAuto(
|
|
108
|
+
grant: Omit<PendingNodeProvision, "token" | "createdAt">,
|
|
109
|
+
): PendingNodeProvision {
|
|
110
|
+
this.sweep();
|
|
111
|
+
const pending: PendingNodeProvision = {
|
|
112
|
+
...grant,
|
|
113
|
+
...sanitizedName(grant.name),
|
|
114
|
+
token: randomBytes(24).toString("base64url"),
|
|
115
|
+
createdAt: Date.now(),
|
|
116
|
+
};
|
|
117
|
+
this.pending.set(pending.token, pending);
|
|
118
|
+
return pending;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Whether `token` is a live auto grant still waiting for its target. */
|
|
122
|
+
isPending(token: string): boolean {
|
|
123
|
+
this.sweep();
|
|
124
|
+
return this.pending.has(token);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Bind an auto grant to the binary for its host. It becomes an ordinary
|
|
129
|
+
* grant under the same token (same TTL clock); a second pin is a no-op.
|
|
130
|
+
*/
|
|
131
|
+
pin(token: string, target: NodeProvisionTarget): boolean {
|
|
132
|
+
this.sweep();
|
|
133
|
+
const pending = this.pending.get(token);
|
|
134
|
+
if (!pending) return false;
|
|
135
|
+
this.pending.delete(token);
|
|
136
|
+
this.grants.set(token, {
|
|
137
|
+
...pending,
|
|
138
|
+
...target,
|
|
139
|
+
scriptUsed: false,
|
|
140
|
+
binaryUsed: false,
|
|
141
|
+
});
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
|
|
87
145
|
/** Serve the installer script for a live grant — once. */
|
|
88
146
|
openScript(token: string): { script: string; filename: string } | null {
|
|
89
147
|
const grant = this.claim(token, "scriptUsed");
|
|
@@ -121,9 +179,20 @@ export class NodeProvisionStore {
|
|
|
121
179
|
for (const [token, grant] of this.grants) {
|
|
122
180
|
if (grant.createdAt < cutoff) this.grants.delete(token);
|
|
123
181
|
}
|
|
182
|
+
for (const [token, grant] of this.pending) {
|
|
183
|
+
if (grant.createdAt < cutoff) this.pending.delete(token);
|
|
184
|
+
}
|
|
124
185
|
}
|
|
125
186
|
}
|
|
126
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Device names are injected into generated shell/PowerShell text — keep them
|
|
190
|
+
* to characters that can't break out of a quoted string.
|
|
191
|
+
*/
|
|
192
|
+
function sanitizedName(name: string | undefined): { name?: string } {
|
|
193
|
+
return name ? { name: name.replace(/[^\w .-]+/g, "").slice(0, 64) } : {};
|
|
194
|
+
}
|
|
195
|
+
|
|
127
196
|
/**
|
|
128
197
|
* Anything that could end, expand, or escape inside a quoted sh,
|
|
129
198
|
* PowerShell, or cmd.exe string: both quote kinds, `$`, backtick,
|
|
@@ -257,6 +326,46 @@ export function installOneLiner(grant: NodeProvisionGrant): string {
|
|
|
257
326
|
);
|
|
258
327
|
}
|
|
259
328
|
|
|
329
|
+
/**
|
|
330
|
+
* The two commands for an auto grant — one per shell family, pinned exactly
|
|
331
|
+
* like {@link installOneLiner}. Each appends its host's platform to the
|
|
332
|
+
* script fetch; the bridge maps `Linux`/`Darwin`, `x86_64`/`aarch64`/
|
|
333
|
+
* `armv7l`, and PowerShell's `AMD64`/`ARM64` through the same normalizers
|
|
334
|
+
* the tool arguments use.
|
|
335
|
+
*
|
|
336
|
+
* The PowerShell line carries one `$` (`$env:PROCESSOR_ARCHITECTURE`): from
|
|
337
|
+
* cmd.exe it reaches PowerShell verbatim, and from a PowerShell prompt the
|
|
338
|
+
* outer double quotes expand it first — the same value either way.
|
|
339
|
+
*/
|
|
340
|
+
export function autoInstallOneLiners(
|
|
341
|
+
grant: Pick<
|
|
342
|
+
NodeProvisionGrant,
|
|
343
|
+
"bridgeUrl" | "token" | "fingerprint" | "spkiPin"
|
|
344
|
+
>,
|
|
345
|
+
): { posix: string; windows: string } {
|
|
346
|
+
const url = `${grant.bridgeUrl}/node/install?provision=${grant.token}`;
|
|
347
|
+
const posix = `curl ${curlFlags(grant.spkiPin)} "${sh(url)}&os=$(uname -s)&arch=$(uname -m)" | sh`;
|
|
348
|
+
const psUrl = `('${psq(url)}&os=windows&arch=' + $env:PROCESSOR_ARCHITECTURE)`;
|
|
349
|
+
const windows = grant.fingerprint
|
|
350
|
+
? `powershell -ExecutionPolicy Bypass -Command "` +
|
|
351
|
+
`${psPinSetup(grant.fingerprint)}; ` +
|
|
352
|
+
`iex ([TalonPin]::Get${psUrl})"`
|
|
353
|
+
: `powershell -ExecutionPolicy Bypass -Command "` +
|
|
354
|
+
`[Net.ServicePointManager]::ServerCertificateValidationCallback={$true}; ` +
|
|
355
|
+
`iwr -UseBasicParsing ${psUrl} | Select-Object -ExpandProperty Content | iex"`;
|
|
356
|
+
return { posix, windows };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* What an auto grant serves when the host's platform has no build or the
|
|
361
|
+
* binary can't be resolved: a script that fails loudly. Valid as both POSIX
|
|
362
|
+
* sh and PowerShell, since which shell is piping it isn't known.
|
|
363
|
+
*/
|
|
364
|
+
export function installRefusalScript(reason: string): string {
|
|
365
|
+
const safe = reason.replace(/["'`$\\%\n\r]+/g, " ").slice(0, 300);
|
|
366
|
+
return `echo "talon-node install refused: ${safe}"\nexit 1\n`;
|
|
367
|
+
}
|
|
368
|
+
|
|
260
369
|
/**
|
|
261
370
|
* POSIX installer: fetch the binary over the same grant (pinned like the
|
|
262
371
|
* script fetch), verify its digest, install it next to the node's config,
|
|
@@ -235,12 +235,20 @@ export const meshTools: ToolDefinition[] = [
|
|
|
235
235
|
{
|
|
236
236
|
name: "make_node_install_link",
|
|
237
237
|
description:
|
|
238
|
-
"Mint a single-use install link served by this daemon's bridge and return the one command that attaches a fresh Linux/macOS/Windows host to the mesh as a headless talon-node. Running it on the host downloads the installer script and binary from the bridge (sha256-verified), installs talon-node, pre-pins the bridge TLS certificate, embeds the bearer token, and registers a boot service — no toolchain, package manager, or manual config on the host. The link expires in 30 minutes and each leg serves exactly once; the host only needs to reach the bridge URL. Requires the native bridge running on a non-loopback bind with a token.",
|
|
238
|
+
"Mint a single-use install link served by this daemon's bridge and return the one command that attaches a fresh Linux/macOS/Windows host to the mesh as a headless talon-node. Running it on the host downloads the installer script and binary from the bridge (sha256-verified), installs talon-node, pre-pins the bridge TLS certificate, embeds the bearer token, and registers a boot service — no toolchain, package manager, or manual config on the host. Omit os and arch when you don't know the host: you get a POSIX and a PowerShell command, and whichever runs reports its own platform so the bridge picks the right binary. The link expires in 30 minutes and each leg serves exactly once; the host only needs to reach the bridge URL. Requires the native bridge running on a non-loopback bind with a token.",
|
|
239
239
|
schema: {
|
|
240
|
-
os: z
|
|
240
|
+
os: z
|
|
241
|
+
.string()
|
|
242
|
+
.optional()
|
|
243
|
+
.describe(
|
|
244
|
+
"Host OS: linux, macos/darwin, or windows. Omit (with arch) to auto-detect on the host.",
|
|
245
|
+
),
|
|
241
246
|
arch: z
|
|
242
247
|
.string()
|
|
243
|
-
.
|
|
248
|
+
.optional()
|
|
249
|
+
.describe(
|
|
250
|
+
"Host arch: amd64/x86_64, arm64/aarch64, or arm. Omit (with os) to auto-detect on the host.",
|
|
251
|
+
),
|
|
244
252
|
name: z
|
|
245
253
|
.string()
|
|
246
254
|
.optional()
|
|
@@ -251,7 +259,7 @@ export const meshTools: ToolDefinition[] = [
|
|
|
251
259
|
.string()
|
|
252
260
|
.optional()
|
|
253
261
|
.describe(
|
|
254
|
-
"Bridge base URL as reachable FROM the new host (e.g. https://100.64.0.7:19880). Default: derived from the bridge bind (wildcard binds use this host's first external IPv4).",
|
|
262
|
+
"Bridge base URL as reachable FROM the new host (e.g. https://100.64.0.7:19880). Default: native.publicUrl when set, else derived from the bridge bind (wildcard binds use this host's first external IPv4).",
|
|
255
263
|
),
|
|
256
264
|
},
|
|
257
265
|
execute: (params, bridge) => bridge("make_node_install_link", params),
|
|
@@ -149,8 +149,18 @@ export type BridgeServerHandlers = {
|
|
|
149
149
|
token: string,
|
|
150
150
|
format: "html" | "json",
|
|
151
151
|
): { contentType: string; body: string } | null;
|
|
152
|
-
/**
|
|
153
|
-
|
|
152
|
+
/**
|
|
153
|
+
* Resolve a node-provisioning token to its installer script, or null. An
|
|
154
|
+
* auto link passes the os/arch its host reported.
|
|
155
|
+
*/
|
|
156
|
+
openNodeInstall(
|
|
157
|
+
token: string,
|
|
158
|
+
os?: string | null,
|
|
159
|
+
arch?: string | null,
|
|
160
|
+
):
|
|
161
|
+
| { script: string; filename: string }
|
|
162
|
+
| null
|
|
163
|
+
| Promise<{ script: string; filename: string } | null>;
|
|
154
164
|
/** Resolve a node-provisioning token to the binary to stream, or null. */
|
|
155
165
|
openNodeBinary(token: string): { path: string; size: number } | null;
|
|
156
166
|
};
|
|
@@ -73,9 +73,15 @@ export function preAuthRoutes(
|
|
|
73
73
|
// the single-use grant token (minted by make_node_install_link,
|
|
74
74
|
// expiring, one serve per leg) is the entire authorization, the same
|
|
75
75
|
// trust model as streamed-transfer tokens.
|
|
76
|
-
"GET /node/install": ({ res, url }) => {
|
|
76
|
+
"GET /node/install": async ({ res, url }) => {
|
|
77
77
|
const token = url.searchParams.get("provision") ?? "";
|
|
78
|
-
const install = token
|
|
78
|
+
const install = token
|
|
79
|
+
? await h.openNodeInstall(
|
|
80
|
+
token,
|
|
81
|
+
url.searchParams.get("os"),
|
|
82
|
+
url.searchParams.get("arch"),
|
|
83
|
+
)
|
|
84
|
+
: null;
|
|
79
85
|
if (!install) return host.unknownProvision(res);
|
|
80
86
|
res.writeHead(200, {
|
|
81
87
|
"Content-Type": "text/plain; charset=utf-8",
|
|
@@ -191,7 +191,7 @@ export function buildBridgeHandlers(
|
|
|
191
191
|
openFileDownload: (token, fromDeviceId) =>
|
|
192
192
|
mesh.openFileDownload(token, fromDeviceId),
|
|
193
193
|
openCompanionPair: (token, format) => mesh.openCompanionPair(token, format),
|
|
194
|
-
openNodeInstall: (token) => mesh.openNodeInstall(token),
|
|
194
|
+
openNodeInstall: (token, os, arch) => mesh.openNodeInstall(token, os, arch),
|
|
195
195
|
openNodeBinary: (token) => mesh.openNodeBinary(token),
|
|
196
196
|
};
|
|
197
197
|
}
|