@matelink/cli 2026.4.17 → 2026.4.19
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/bin/matecli.mjs +191 -21
- package/package.json +1 -1
package/bin/matecli.mjs
CHANGED
|
@@ -53,6 +53,102 @@ const DEFAULT_GATEWAY_SCOPES = [
|
|
|
53
53
|
].join(",");
|
|
54
54
|
const SESSION_CONTEXT_MIN_TOKENS = 1024;
|
|
55
55
|
const CLI_ENTRY = fileURLToPath(import.meta.url);
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
function buildPathString(values) {
|
|
59
|
+
const entries = [];
|
|
60
|
+
const seen = new Set();
|
|
61
|
+
for (const value of values) {
|
|
62
|
+
const raw = String(value ?? "").trim();
|
|
63
|
+
if (!raw) continue;
|
|
64
|
+
for (const part of raw.split(":")) {
|
|
65
|
+
const normalized = part.trim();
|
|
66
|
+
if (!normalized || seen.has(normalized)) continue;
|
|
67
|
+
seen.add(normalized);
|
|
68
|
+
entries.push(normalized);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return entries.join(":");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function resolveSiblingBinDirs(executablePath) {
|
|
75
|
+
const normalized = String(executablePath ?? "").trim();
|
|
76
|
+
if (!normalized) return [];
|
|
77
|
+
const binDir = path.dirname(normalized);
|
|
78
|
+
return [binDir];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function resolveOpenClawBinaryCandidates() {
|
|
82
|
+
const homeDir = os.homedir();
|
|
83
|
+
const candidates = new Set();
|
|
84
|
+
const addCandidate = (candidate) => {
|
|
85
|
+
const normalized = String(candidate ?? "").trim();
|
|
86
|
+
if (!normalized) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
if (fs.existsSync(normalized)) {
|
|
91
|
+
candidates.add(fs.realpathSync(normalized));
|
|
92
|
+
}
|
|
93
|
+
} catch {
|
|
94
|
+
if (fs.existsSync(normalized)) {
|
|
95
|
+
candidates.add(normalized);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
for (const nodeCandidate of resolveNodeCandidates()) {
|
|
101
|
+
addCandidate(path.join(path.dirname(nodeCandidate), "openclaw"));
|
|
102
|
+
}
|
|
103
|
+
addCandidate(path.join(homeDir, ".nvm", "current", "bin", "openclaw"));
|
|
104
|
+
addCandidate(path.join(homeDir, ".volta", "bin", "openclaw"));
|
|
105
|
+
addCandidate("/opt/homebrew/bin/openclaw");
|
|
106
|
+
addCandidate("/usr/local/bin/openclaw");
|
|
107
|
+
addCandidate("/usr/bin/openclaw");
|
|
108
|
+
|
|
109
|
+
const whichOpenClaw = runCommand("which", ["openclaw"]);
|
|
110
|
+
if (whichOpenClaw.status === 0) {
|
|
111
|
+
const resolved = String(whichOpenClaw.stdout ?? "").trim();
|
|
112
|
+
if (resolved && fs.existsSync(resolved)) {
|
|
113
|
+
addCandidate(resolved);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return [...candidates];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function resolveOpenClawBinaryPath() {
|
|
121
|
+
for (const candidate of resolveOpenClawBinaryCandidates()) {
|
|
122
|
+
if (candidate && fs.existsSync(candidate)) {
|
|
123
|
+
return candidate;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return "openclaw";
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function buildBridgeRuntimeEnv(extraEnv = {}) {
|
|
130
|
+
const homeDir = os.homedir();
|
|
131
|
+
const nodePath = resolveBridgeServiceNodePath();
|
|
132
|
+
const openclawPath = resolveOpenClawBinaryPath();
|
|
133
|
+
const pathValue = buildPathString([
|
|
134
|
+
...resolveSiblingBinDirs(nodePath),
|
|
135
|
+
...resolveSiblingBinDirs(openclawPath),
|
|
136
|
+
"/opt/homebrew/bin",
|
|
137
|
+
"/usr/local/bin",
|
|
138
|
+
"/usr/bin",
|
|
139
|
+
"/bin",
|
|
140
|
+
"/usr/sbin",
|
|
141
|
+
"/sbin",
|
|
142
|
+
path.join(homeDir, ".volta", "bin"),
|
|
143
|
+
path.join(homeDir, ".nvm", "current", "bin"),
|
|
144
|
+
process.env.PATH ?? "",
|
|
145
|
+
]);
|
|
146
|
+
return {
|
|
147
|
+
...process.env,
|
|
148
|
+
...extraEnv,
|
|
149
|
+
PATH: pathValue,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
56
152
|
const CLI_LANGUAGE = detectCliLanguage();
|
|
57
153
|
const CLI_I18N = {
|
|
58
154
|
zh: {
|
|
@@ -728,6 +824,7 @@ function listFixedMemoryFiles(workspaceRoot) {
|
|
|
728
824
|
workspaceRoot,
|
|
729
825
|
relativePath,
|
|
730
826
|
absolutePath,
|
|
827
|
+
includeContent: true,
|
|
731
828
|
});
|
|
732
829
|
});
|
|
733
830
|
}
|
|
@@ -1375,6 +1472,40 @@ function extractErrorMessage(decoded, fallback) {
|
|
|
1375
1472
|
return fallback;
|
|
1376
1473
|
}
|
|
1377
1474
|
|
|
1475
|
+
function formatErrorDetails(error) {
|
|
1476
|
+
if (!(error instanceof Error)) {
|
|
1477
|
+
return String(error ?? "");
|
|
1478
|
+
}
|
|
1479
|
+
const details = [];
|
|
1480
|
+
if (error.name && error.name !== "Error") {
|
|
1481
|
+
details.push(`name=${error.name}`);
|
|
1482
|
+
}
|
|
1483
|
+
if (error.code) {
|
|
1484
|
+
details.push(`code=${String(error.code)}`);
|
|
1485
|
+
}
|
|
1486
|
+
if (error.errno) {
|
|
1487
|
+
details.push(`errno=${String(error.errno)}`);
|
|
1488
|
+
}
|
|
1489
|
+
if (error.type) {
|
|
1490
|
+
details.push(`type=${String(error.type)}`);
|
|
1491
|
+
}
|
|
1492
|
+
if (error.cause && typeof error.cause === "object") {
|
|
1493
|
+
const cause = error.cause;
|
|
1494
|
+
if (cause?.code) {
|
|
1495
|
+
details.push(`cause.code=${String(cause.code)}`);
|
|
1496
|
+
}
|
|
1497
|
+
if (cause?.errno) {
|
|
1498
|
+
details.push(`cause.errno=${String(cause.errno)}`);
|
|
1499
|
+
}
|
|
1500
|
+
if (cause?.message) {
|
|
1501
|
+
details.push(`cause=${String(cause.message)}`);
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
return details.length > 0
|
|
1505
|
+
? `${error.message} [${details.join(", ")}]`
|
|
1506
|
+
: error.message;
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1378
1509
|
async function fetchWithTimeout(url, options = {}, timeoutMs = DEFAULT_NETWORK_TIMEOUT_MS) {
|
|
1379
1510
|
const controller = new AbortController();
|
|
1380
1511
|
const timer = setTimeout(() => controller.abort(new Error(`fetch timeout after ${timeoutMs}ms`)), timeoutMs);
|
|
@@ -1383,6 +1514,8 @@ async function fetchWithTimeout(url, options = {}, timeoutMs = DEFAULT_NETWORK_T
|
|
|
1383
1514
|
...options,
|
|
1384
1515
|
signal: controller.signal,
|
|
1385
1516
|
});
|
|
1517
|
+
} catch (error) {
|
|
1518
|
+
throw new Error(formatErrorDetails(error));
|
|
1386
1519
|
} finally {
|
|
1387
1520
|
clearTimeout(timer);
|
|
1388
1521
|
}
|
|
@@ -1756,7 +1889,9 @@ function resolveBridgeServiceNodePath() {
|
|
|
1756
1889
|
function writeBridgeLaunchScript(installConfig) {
|
|
1757
1890
|
const scriptPath = resolveBridgeLaunchScriptPath();
|
|
1758
1891
|
fs.mkdirSync(path.dirname(scriptPath), { recursive: true });
|
|
1759
|
-
const defaultPath = [
|
|
1892
|
+
const defaultPath = buildPathString([
|
|
1893
|
+
...resolveSiblingBinDirs(installConfig.nodePath),
|
|
1894
|
+
...resolveSiblingBinDirs(installConfig.openclawPath),
|
|
1760
1895
|
"/opt/homebrew/bin",
|
|
1761
1896
|
"/usr/local/bin",
|
|
1762
1897
|
"/usr/bin",
|
|
@@ -1765,11 +1900,16 @@ function writeBridgeLaunchScript(installConfig) {
|
|
|
1765
1900
|
"/sbin",
|
|
1766
1901
|
path.join(os.homedir(), ".volta", "bin"),
|
|
1767
1902
|
path.join(os.homedir(), ".nvm", "current", "bin"),
|
|
1768
|
-
]
|
|
1769
|
-
const knownNodeCandidates = resolveNodeCandidates()
|
|
1770
|
-
|
|
1771
|
-
.
|
|
1772
|
-
|
|
1903
|
+
]);
|
|
1904
|
+
const knownNodeCandidates = [installConfig.nodePath, ...resolveNodeCandidates()]
|
|
1905
|
+
.filter(Boolean)
|
|
1906
|
+
.filter((candidate, index, values) => values.indexOf(candidate) === index);
|
|
1907
|
+
const candidateChecks = knownNodeCandidates.length > 0
|
|
1908
|
+
? `${knownNodeCandidates
|
|
1909
|
+
.map((candidate, index) => `${index === 0 ? 'if' : 'elif'} [ -x '${escapePosixShellSingleQuoted(candidate)}' ]; then NODE_BIN='${escapePosixShellSingleQuoted(candidate)}';`)
|
|
1910
|
+
.join("\n")}
|
|
1911
|
+
fi`
|
|
1912
|
+
: "";
|
|
1773
1913
|
const fallbackLine = buildPosixShellExec("node", installConfig.args);
|
|
1774
1914
|
const script = [
|
|
1775
1915
|
"#!/bin/sh",
|
|
@@ -1806,6 +1946,7 @@ function ensureBridgeServiceInstallConfig({ relayUrl, gatewayBaseUrl }) {
|
|
|
1806
1946
|
stdoutLog: path.join(logDir, "bridge.stdout.log"),
|
|
1807
1947
|
stderrLog: path.join(logDir, "bridge.stderr.log"),
|
|
1808
1948
|
nodePath: resolveBridgeServiceNodePath(),
|
|
1949
|
+
openclawPath: resolveOpenClawBinaryPath(),
|
|
1809
1950
|
scriptPath: CLI_ENTRY,
|
|
1810
1951
|
args: bridgeInvocationArgs({ relayUrl, gatewayBaseUrl }),
|
|
1811
1952
|
};
|
|
@@ -2610,7 +2751,7 @@ async function readRelayNextGatewayRequest({
|
|
|
2610
2751
|
if (message.includes("timeout")) {
|
|
2611
2752
|
return null;
|
|
2612
2753
|
}
|
|
2613
|
-
throw
|
|
2754
|
+
throw new Error(`relay gateway poll transport failed: GET ${url} -> ${message}`);
|
|
2614
2755
|
}
|
|
2615
2756
|
|
|
2616
2757
|
if (response.status === 204) {
|
|
@@ -3011,13 +3152,11 @@ function createGatewayWsClient({ gatewayBaseUrl, gatewayAuthToken }) {
|
|
|
3011
3152
|
connectTimer = null;
|
|
3012
3153
|
}
|
|
3013
3154
|
const role = "operator";
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
"operator.pairing",
|
|
3020
|
-
];
|
|
3155
|
+
// The bridge WS client only performs read-side gateway RPCs such as
|
|
3156
|
+
// config.get, sessions.list, skills.status, and chat.history. Asking
|
|
3157
|
+
// for broader scopes causes local token-mode gateways to reject the
|
|
3158
|
+
// connection with a scope-upgrade/pairing-required error.
|
|
3159
|
+
const scopes = ["operator.read"];
|
|
3021
3160
|
const clientId = "openclaw-macos";
|
|
3022
3161
|
const clientMode = "ui";
|
|
3023
3162
|
const signedAtMs = Date.now();
|
|
@@ -3146,6 +3285,11 @@ async function callGatewayRpcLocal({
|
|
|
3146
3285
|
return callWorkspaceRpcLocal({ method, params });
|
|
3147
3286
|
}
|
|
3148
3287
|
|
|
3288
|
+
if (method === "config.get" || method === "sessions.list") {
|
|
3289
|
+
const client = getOrCreateGatewayWsClient({ gatewayBaseUrl, gatewayAuthToken });
|
|
3290
|
+
return client.call(method, params ?? {});
|
|
3291
|
+
}
|
|
3292
|
+
|
|
3149
3293
|
// Prefer CLI first for broad compatibility, but fall back to the persistent
|
|
3150
3294
|
// WS client when the CLI gateway call hits transient websocket closures.
|
|
3151
3295
|
try {
|
|
@@ -3172,7 +3316,10 @@ function shouldFallbackGatewayRpcViaWs(error) {
|
|
|
3172
3316
|
message.includes("(1006") ||
|
|
3173
3317
|
message.includes("no close reason") ||
|
|
3174
3318
|
message.includes("gateway ws error") ||
|
|
3175
|
-
message.includes("gateway disconnected")
|
|
3319
|
+
message.includes("gateway disconnected") ||
|
|
3320
|
+
message.includes("spawn openclaw enoent") ||
|
|
3321
|
+
message.includes("enoent") ||
|
|
3322
|
+
message.includes("gateway call timed out")
|
|
3176
3323
|
);
|
|
3177
3324
|
}
|
|
3178
3325
|
|
|
@@ -3199,8 +3346,9 @@ async function callGatewayRpcLocalViaCli({
|
|
|
3199
3346
|
args.push("--token", gatewayAuthToken);
|
|
3200
3347
|
}
|
|
3201
3348
|
|
|
3202
|
-
const child = spawn(
|
|
3349
|
+
const child = spawn(resolveOpenClawBinaryPath(), args, {
|
|
3203
3350
|
stdio: ["ignore", "pipe", "pipe"],
|
|
3351
|
+
env: buildBridgeRuntimeEnv(),
|
|
3204
3352
|
});
|
|
3205
3353
|
let timedOut = false;
|
|
3206
3354
|
const timeout = setTimeout(() => {
|
|
@@ -3212,7 +3360,7 @@ async function callGatewayRpcLocalViaCli({
|
|
|
3212
3360
|
}
|
|
3213
3361
|
}, GATEWAY_RPC_CLI_TIMEOUT_MS);
|
|
3214
3362
|
|
|
3215
|
-
const [stdoutChunks, stderrChunks] = await Promise.all([
|
|
3363
|
+
const [stdoutChunks, stderrChunks, spawnError] = await Promise.all([
|
|
3216
3364
|
new Promise((resolve) => {
|
|
3217
3365
|
const chunks = [];
|
|
3218
3366
|
child.stdout?.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
@@ -3225,10 +3373,18 @@ async function callGatewayRpcLocalViaCli({
|
|
|
3225
3373
|
child.stderr?.on("end", () => resolve(chunks));
|
|
3226
3374
|
child.stderr?.on("error", () => resolve(chunks));
|
|
3227
3375
|
}),
|
|
3376
|
+
new Promise((resolve) => {
|
|
3377
|
+
child.once("error", (error) => resolve(error));
|
|
3378
|
+
child.once("spawn", () => resolve(null));
|
|
3379
|
+
}),
|
|
3228
3380
|
new Promise((resolve) => child.on("close", resolve)),
|
|
3229
3381
|
]);
|
|
3230
3382
|
clearTimeout(timeout);
|
|
3231
3383
|
|
|
3384
|
+
if (spawnError instanceof Error) {
|
|
3385
|
+
throw spawnError;
|
|
3386
|
+
}
|
|
3387
|
+
|
|
3232
3388
|
const stdoutText = Buffer.concat(stdoutChunks).toString("utf8");
|
|
3233
3389
|
const stderrText = Buffer.concat(stderrChunks).toString("utf8");
|
|
3234
3390
|
if (timedOut) {
|
|
@@ -3453,6 +3609,11 @@ async function proxyGatewayRequestLocal({
|
|
|
3453
3609
|
const reader = response.body.getReader();
|
|
3454
3610
|
const decoder = new TextDecoder();
|
|
3455
3611
|
|
|
3612
|
+
// Fire-and-forget chunk publishing: don't await each POST so the reader
|
|
3613
|
+
// is never blocked by network round-trips. We keep an ordered queue of
|
|
3614
|
+
// in-flight promises and drain it before sending the final "end" event.
|
|
3615
|
+
const inflightChunks = [];
|
|
3616
|
+
|
|
3456
3617
|
while (true) {
|
|
3457
3618
|
const { done, value } = await reader.read();
|
|
3458
3619
|
if (done) {
|
|
@@ -3462,7 +3623,7 @@ async function proxyGatewayRequestLocal({
|
|
|
3462
3623
|
if (!text) {
|
|
3463
3624
|
continue;
|
|
3464
3625
|
}
|
|
3465
|
-
|
|
3626
|
+
const p = publishRelayGatewayEvent({
|
|
3466
3627
|
relayUrl,
|
|
3467
3628
|
gatewayId,
|
|
3468
3629
|
gatewayToken,
|
|
@@ -3471,12 +3632,15 @@ async function proxyGatewayRequestLocal({
|
|
|
3471
3632
|
event: "chunk",
|
|
3472
3633
|
data: text,
|
|
3473
3634
|
},
|
|
3635
|
+
}).catch((err) => {
|
|
3636
|
+
console.error(`[relay chunk publish error] ${err?.message || err}`);
|
|
3474
3637
|
});
|
|
3638
|
+
inflightChunks.push(p);
|
|
3475
3639
|
}
|
|
3476
3640
|
|
|
3477
3641
|
const tail = decoder.decode();
|
|
3478
3642
|
if (tail) {
|
|
3479
|
-
|
|
3643
|
+
const p = publishRelayGatewayEvent({
|
|
3480
3644
|
relayUrl,
|
|
3481
3645
|
gatewayId,
|
|
3482
3646
|
gatewayToken,
|
|
@@ -3485,9 +3649,15 @@ async function proxyGatewayRequestLocal({
|
|
|
3485
3649
|
event: "chunk",
|
|
3486
3650
|
data: tail,
|
|
3487
3651
|
},
|
|
3652
|
+
}).catch((err) => {
|
|
3653
|
+
console.error(`[relay tail publish error] ${err?.message || err}`);
|
|
3488
3654
|
});
|
|
3655
|
+
inflightChunks.push(p);
|
|
3489
3656
|
}
|
|
3490
3657
|
|
|
3658
|
+
// Wait for all chunk publishes to complete before sending "end".
|
|
3659
|
+
await Promise.all(inflightChunks);
|
|
3660
|
+
|
|
3491
3661
|
await publishRelayGatewayEvent({
|
|
3492
3662
|
relayUrl,
|
|
3493
3663
|
gatewayId,
|
|
@@ -3629,12 +3799,12 @@ function startRelayBridgeDetached({
|
|
|
3629
3799
|
}
|
|
3630
3800
|
|
|
3631
3801
|
const child = spawn(
|
|
3632
|
-
|
|
3802
|
+
resolveBridgeServiceNodePath(),
|
|
3633
3803
|
[CLI_ENTRY, "bridge", "--relay", relayUrl, "--gateway", gatewayBaseUrl],
|
|
3634
3804
|
{
|
|
3635
3805
|
detached: true,
|
|
3636
3806
|
stdio: "ignore",
|
|
3637
|
-
env:
|
|
3807
|
+
env: buildBridgeRuntimeEnv(),
|
|
3638
3808
|
windowsHide: process.platform === "win32",
|
|
3639
3809
|
},
|
|
3640
3810
|
);
|