mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-13 19:10:39 +00:00
119 lines
3.4 KiB
TypeScript
119 lines
3.4 KiB
TypeScript
import type { Command } from "commander";
|
|
import { callGateway } from "../../gateway/call.js";
|
|
import {
|
|
GATEWAY_CLIENT_MODES,
|
|
GATEWAY_CLIENT_NAMES,
|
|
} from "../../utils/message-channel.js";
|
|
import { withProgress } from "../progress.js";
|
|
import { parseNodeList, parsePairingList } from "./format.js";
|
|
import type { NodeListNode, NodesRpcOpts } from "./types.js";
|
|
|
|
export const nodesCallOpts = (
|
|
cmd: Command,
|
|
defaults?: { timeoutMs?: number },
|
|
) =>
|
|
cmd
|
|
.option(
|
|
"--url <url>",
|
|
"Gateway WebSocket URL (defaults to gateway.remote.url when configured)",
|
|
)
|
|
.option("--token <token>", "Gateway token (if required)")
|
|
.option(
|
|
"--timeout <ms>",
|
|
"Timeout in ms",
|
|
String(defaults?.timeoutMs ?? 10_000),
|
|
)
|
|
.option("--json", "Output JSON", false);
|
|
|
|
export const callGatewayCli = async (
|
|
method: string,
|
|
opts: NodesRpcOpts,
|
|
params?: unknown,
|
|
) =>
|
|
withProgress(
|
|
{
|
|
label: `Nodes ${method}`,
|
|
indeterminate: true,
|
|
enabled: opts.json !== true,
|
|
},
|
|
async () =>
|
|
await callGateway({
|
|
url: opts.url,
|
|
token: opts.token,
|
|
method,
|
|
params,
|
|
timeoutMs: Number(opts.timeout ?? 10_000),
|
|
clientName: GATEWAY_CLIENT_NAMES.CLI,
|
|
mode: GATEWAY_CLIENT_MODES.CLI,
|
|
}),
|
|
);
|
|
|
|
export function unauthorizedHintForMessage(message: string): string | null {
|
|
const haystack = message.toLowerCase();
|
|
if (
|
|
haystack.includes("unauthorizedclient") ||
|
|
haystack.includes("bridge client is not authorized") ||
|
|
haystack.includes("unsigned bridge clients are not allowed")
|
|
) {
|
|
return [
|
|
"peekaboo bridge rejected the client.",
|
|
"sign the peekaboo CLI (TeamID Y5PE65HELJ) or launch the host with",
|
|
"PEEKABOO_ALLOW_UNSIGNED_SOCKET_CLIENTS=1 for local dev.",
|
|
].join(" ");
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function normalizeNodeKey(value: string) {
|
|
return value
|
|
.toLowerCase()
|
|
.replace(/[^a-z0-9]+/g, "-")
|
|
.replace(/^-+/, "")
|
|
.replace(/-+$/, "");
|
|
}
|
|
|
|
export async function resolveNodeId(opts: NodesRpcOpts, query: string) {
|
|
const q = String(query ?? "").trim();
|
|
if (!q) throw new Error("node required");
|
|
|
|
let nodes: NodeListNode[] = [];
|
|
try {
|
|
const res = (await callGatewayCli("node.list", opts, {})) as unknown;
|
|
nodes = parseNodeList(res);
|
|
} catch {
|
|
const res = (await callGatewayCli("node.pair.list", opts, {})) as unknown;
|
|
const { paired } = parsePairingList(res);
|
|
nodes = paired.map((n) => ({
|
|
nodeId: n.nodeId,
|
|
displayName: n.displayName,
|
|
platform: n.platform,
|
|
version: n.version,
|
|
remoteIp: n.remoteIp,
|
|
}));
|
|
}
|
|
|
|
const qNorm = normalizeNodeKey(q);
|
|
const matches = nodes.filter((n) => {
|
|
if (n.nodeId === q) return true;
|
|
if (typeof n.remoteIp === "string" && n.remoteIp === q) return true;
|
|
const name = typeof n.displayName === "string" ? n.displayName : "";
|
|
if (name && normalizeNodeKey(name) === qNorm) return true;
|
|
if (q.length >= 6 && n.nodeId.startsWith(q)) return true;
|
|
return false;
|
|
});
|
|
|
|
if (matches.length === 1) return matches[0].nodeId;
|
|
if (matches.length === 0) {
|
|
const known = nodes
|
|
.map((n) => n.displayName || n.remoteIp || n.nodeId)
|
|
.filter(Boolean)
|
|
.join(", ");
|
|
throw new Error(`unknown node: ${q}${known ? ` (known: ${known})` : ""}`);
|
|
}
|
|
throw new Error(
|
|
`ambiguous node: ${q} (matches: ${matches
|
|
.map((n) => n.displayName || n.remoteIp || n.nodeId)
|
|
.join(", ")})`,
|
|
);
|
|
}
|