@matelink/cli 2026.4.16 → 2026.4.18
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 +246 -12
- package/package.json +1 -1
package/bin/matecli.mjs
CHANGED
|
@@ -14,7 +14,7 @@ import fs from "node:fs";
|
|
|
14
14
|
import os from "node:os";
|
|
15
15
|
import path from "node:path";
|
|
16
16
|
import process from "node:process";
|
|
17
|
-
import { fileURLToPath } from "node:url";
|
|
17
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
18
18
|
|
|
19
19
|
const NUMERIC_CODE_LENGTH = 4;
|
|
20
20
|
const GROUP_SIZE = 4;
|
|
@@ -51,7 +51,104 @@ const DEFAULT_GATEWAY_SCOPES = [
|
|
|
51
51
|
"operator.approvals",
|
|
52
52
|
"operator.pairing",
|
|
53
53
|
].join(",");
|
|
54
|
+
const SESSION_CONTEXT_MIN_TOKENS = 1024;
|
|
54
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
|
+
}
|
|
55
152
|
const CLI_LANGUAGE = detectCliLanguage();
|
|
56
153
|
const CLI_I18N = {
|
|
57
154
|
zh: {
|
|
@@ -446,6 +543,18 @@ function resolveOpenClawConfigPath() {
|
|
|
446
543
|
return path.join(resolveOpenClawHome(), "openclaw.json");
|
|
447
544
|
}
|
|
448
545
|
|
|
546
|
+
function resolveOpenClawDistPath(fileName) {
|
|
547
|
+
return path.join(
|
|
548
|
+
resolveOpenClawHome(),
|
|
549
|
+
"extensions",
|
|
550
|
+
"memory-tdai",
|
|
551
|
+
"node_modules",
|
|
552
|
+
"openclaw",
|
|
553
|
+
"dist",
|
|
554
|
+
fileName,
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
|
|
449
558
|
function resolveTestNextIMStatePath() {
|
|
450
559
|
return path.join(resolveOpenClawHome(), "testnextim.state.json");
|
|
451
560
|
}
|
|
@@ -546,6 +655,21 @@ function resolveConfiguredWorkspaceRoot() {
|
|
|
546
655
|
return path.resolve(resolved);
|
|
547
656
|
}
|
|
548
657
|
|
|
658
|
+
let sessionStoreHelpersPromise = null;
|
|
659
|
+
|
|
660
|
+
async function loadSessionStoreHelpers() {
|
|
661
|
+
if (!sessionStoreHelpersPromise) {
|
|
662
|
+
sessionStoreHelpersPromise = Promise.all([
|
|
663
|
+
import(pathToFileURL(resolveOpenClawDistPath("session-utils-PHKOUUva.js")).href),
|
|
664
|
+
import(pathToFileURL(resolveOpenClawDistPath("store-DACjypj4.js")).href),
|
|
665
|
+
]).then(([sessionUtilsMod, storeMod]) => ({
|
|
666
|
+
loadSessionEntry: sessionUtilsMod.s,
|
|
667
|
+
updateSessionStoreEntry: storeMod.f,
|
|
668
|
+
}));
|
|
669
|
+
}
|
|
670
|
+
return sessionStoreHelpersPromise;
|
|
671
|
+
}
|
|
672
|
+
|
|
549
673
|
function normalizeWorkspaceRelativePath(rawName) {
|
|
550
674
|
const value = String(rawName ?? "").trim();
|
|
551
675
|
if (!value) {
|
|
@@ -700,6 +824,7 @@ function listFixedMemoryFiles(workspaceRoot) {
|
|
|
700
824
|
workspaceRoot,
|
|
701
825
|
relativePath,
|
|
702
826
|
absolutePath,
|
|
827
|
+
includeContent: true,
|
|
703
828
|
});
|
|
704
829
|
});
|
|
705
830
|
}
|
|
@@ -814,6 +939,62 @@ async function callWorkspaceRpcLocal({
|
|
|
814
939
|
return null;
|
|
815
940
|
}
|
|
816
941
|
|
|
942
|
+
async function callSessionContextRpcLocal({
|
|
943
|
+
method,
|
|
944
|
+
params,
|
|
945
|
+
}) {
|
|
946
|
+
const sessionKey = String(params?.sessionKey ?? params?.key ?? "").trim();
|
|
947
|
+
if (!sessionKey) {
|
|
948
|
+
throw new Error("session key is required");
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
const { loadSessionEntry, updateSessionStoreEntry } = await loadSessionStoreHelpers();
|
|
952
|
+
const loaded = loadSessionEntry(sessionKey);
|
|
953
|
+
const resolvedSessionKey = String(loaded?.canonicalKey ?? sessionKey).trim() || sessionKey;
|
|
954
|
+
|
|
955
|
+
if (method === "session.context.get") {
|
|
956
|
+
return {
|
|
957
|
+
ok: true,
|
|
958
|
+
sessionKey: resolvedSessionKey,
|
|
959
|
+
contextTokens: Number.isFinite(loaded?.entry?.contextTokens)
|
|
960
|
+
? Math.max(0, Math.floor(loaded.entry.contextTokens))
|
|
961
|
+
: null,
|
|
962
|
+
storePath: loaded?.storePath ?? "",
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
if (method === "session.context.set") {
|
|
967
|
+
if (!loaded?.entry || !String(loaded?.storePath ?? "").trim()) {
|
|
968
|
+
throw new Error(`session not found: ${sessionKey}`);
|
|
969
|
+
}
|
|
970
|
+
const contextTokens = Math.max(
|
|
971
|
+
SESSION_CONTEXT_MIN_TOKENS,
|
|
972
|
+
Math.floor(Number(params?.contextTokens ?? 0) || 0),
|
|
973
|
+
);
|
|
974
|
+
const updated = await updateSessionStoreEntry({
|
|
975
|
+
storePath: loaded.storePath,
|
|
976
|
+
sessionKey: resolvedSessionKey,
|
|
977
|
+
update: async (existing) => ({
|
|
978
|
+
contextTokens,
|
|
979
|
+
updatedAt: Math.max(existing?.updatedAt ?? 0, Date.now()),
|
|
980
|
+
}),
|
|
981
|
+
});
|
|
982
|
+
if (!updated) {
|
|
983
|
+
throw new Error(`session not found: ${sessionKey}`);
|
|
984
|
+
}
|
|
985
|
+
return {
|
|
986
|
+
ok: true,
|
|
987
|
+
sessionKey: resolvedSessionKey,
|
|
988
|
+
contextTokens: Number.isFinite(updated?.contextTokens)
|
|
989
|
+
? Math.max(0, Math.floor(updated.contextTokens))
|
|
990
|
+
: contextTokens,
|
|
991
|
+
storePath: loaded.storePath,
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
return null;
|
|
996
|
+
}
|
|
997
|
+
|
|
817
998
|
function commandToString(command, args) {
|
|
818
999
|
return [command, ...args]
|
|
819
1000
|
.map((part) => {
|
|
@@ -1291,6 +1472,40 @@ function extractErrorMessage(decoded, fallback) {
|
|
|
1291
1472
|
return fallback;
|
|
1292
1473
|
}
|
|
1293
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
|
+
|
|
1294
1509
|
async function fetchWithTimeout(url, options = {}, timeoutMs = DEFAULT_NETWORK_TIMEOUT_MS) {
|
|
1295
1510
|
const controller = new AbortController();
|
|
1296
1511
|
const timer = setTimeout(() => controller.abort(new Error(`fetch timeout after ${timeoutMs}ms`)), timeoutMs);
|
|
@@ -1299,6 +1514,8 @@ async function fetchWithTimeout(url, options = {}, timeoutMs = DEFAULT_NETWORK_T
|
|
|
1299
1514
|
...options,
|
|
1300
1515
|
signal: controller.signal,
|
|
1301
1516
|
});
|
|
1517
|
+
} catch (error) {
|
|
1518
|
+
throw new Error(formatErrorDetails(error));
|
|
1302
1519
|
} finally {
|
|
1303
1520
|
clearTimeout(timer);
|
|
1304
1521
|
}
|
|
@@ -1672,7 +1889,9 @@ function resolveBridgeServiceNodePath() {
|
|
|
1672
1889
|
function writeBridgeLaunchScript(installConfig) {
|
|
1673
1890
|
const scriptPath = resolveBridgeLaunchScriptPath();
|
|
1674
1891
|
fs.mkdirSync(path.dirname(scriptPath), { recursive: true });
|
|
1675
|
-
const defaultPath = [
|
|
1892
|
+
const defaultPath = buildPathString([
|
|
1893
|
+
...resolveSiblingBinDirs(installConfig.nodePath),
|
|
1894
|
+
...resolveSiblingBinDirs(installConfig.openclawPath),
|
|
1676
1895
|
"/opt/homebrew/bin",
|
|
1677
1896
|
"/usr/local/bin",
|
|
1678
1897
|
"/usr/bin",
|
|
@@ -1681,11 +1900,16 @@ function writeBridgeLaunchScript(installConfig) {
|
|
|
1681
1900
|
"/sbin",
|
|
1682
1901
|
path.join(os.homedir(), ".volta", "bin"),
|
|
1683
1902
|
path.join(os.homedir(), ".nvm", "current", "bin"),
|
|
1684
|
-
]
|
|
1685
|
-
const knownNodeCandidates = resolveNodeCandidates()
|
|
1686
|
-
|
|
1687
|
-
.
|
|
1688
|
-
|
|
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
|
+
: "";
|
|
1689
1913
|
const fallbackLine = buildPosixShellExec("node", installConfig.args);
|
|
1690
1914
|
const script = [
|
|
1691
1915
|
"#!/bin/sh",
|
|
@@ -1722,6 +1946,7 @@ function ensureBridgeServiceInstallConfig({ relayUrl, gatewayBaseUrl }) {
|
|
|
1722
1946
|
stdoutLog: path.join(logDir, "bridge.stdout.log"),
|
|
1723
1947
|
stderrLog: path.join(logDir, "bridge.stderr.log"),
|
|
1724
1948
|
nodePath: resolveBridgeServiceNodePath(),
|
|
1949
|
+
openclawPath: resolveOpenClawBinaryPath(),
|
|
1725
1950
|
scriptPath: CLI_ENTRY,
|
|
1726
1951
|
args: bridgeInvocationArgs({ relayUrl, gatewayBaseUrl }),
|
|
1727
1952
|
};
|
|
@@ -2526,7 +2751,7 @@ async function readRelayNextGatewayRequest({
|
|
|
2526
2751
|
if (message.includes("timeout")) {
|
|
2527
2752
|
return null;
|
|
2528
2753
|
}
|
|
2529
|
-
throw
|
|
2754
|
+
throw new Error(`relay gateway poll transport failed: GET ${url} -> ${message}`);
|
|
2530
2755
|
}
|
|
2531
2756
|
|
|
2532
2757
|
if (response.status === 204) {
|
|
@@ -3037,12 +3262,20 @@ async function callGatewayRpcLocal({
|
|
|
3037
3262
|
return {
|
|
3038
3263
|
ok: true,
|
|
3039
3264
|
methods: ["sessions.list", "sessions.abort", "sessions.delete", "sessions.usage", "sessions.patch",
|
|
3040
|
-
"usage.cost", "
|
|
3265
|
+
"usage.cost", "session.context.get", "session.context.set",
|
|
3266
|
+
"memory.files.list", "memory.files.get", "memory.files.set", "agents.files.list", "agents.files.get", "agents.files.set",
|
|
3041
3267
|
"skills.status", "models.list", "chat.history", "chat.abort",
|
|
3042
3268
|
"config.get", "config.patch"],
|
|
3043
3269
|
};
|
|
3044
3270
|
}
|
|
3045
3271
|
|
|
3272
|
+
if (
|
|
3273
|
+
method === "session.context.get" ||
|
|
3274
|
+
method === "session.context.set"
|
|
3275
|
+
) {
|
|
3276
|
+
return callSessionContextRpcLocal({ method, params });
|
|
3277
|
+
}
|
|
3278
|
+
|
|
3046
3279
|
if (
|
|
3047
3280
|
method === "memory.files.list" ||
|
|
3048
3281
|
method === "memory.files.get" ||
|
|
@@ -3107,8 +3340,9 @@ async function callGatewayRpcLocalViaCli({
|
|
|
3107
3340
|
args.push("--token", gatewayAuthToken);
|
|
3108
3341
|
}
|
|
3109
3342
|
|
|
3110
|
-
const child = spawn(
|
|
3343
|
+
const child = spawn(resolveOpenClawBinaryPath(), args, {
|
|
3111
3344
|
stdio: ["ignore", "pipe", "pipe"],
|
|
3345
|
+
env: buildBridgeRuntimeEnv(),
|
|
3112
3346
|
});
|
|
3113
3347
|
let timedOut = false;
|
|
3114
3348
|
const timeout = setTimeout(() => {
|
|
@@ -3537,12 +3771,12 @@ function startRelayBridgeDetached({
|
|
|
3537
3771
|
}
|
|
3538
3772
|
|
|
3539
3773
|
const child = spawn(
|
|
3540
|
-
|
|
3774
|
+
resolveBridgeServiceNodePath(),
|
|
3541
3775
|
[CLI_ENTRY, "bridge", "--relay", relayUrl, "--gateway", gatewayBaseUrl],
|
|
3542
3776
|
{
|
|
3543
3777
|
detached: true,
|
|
3544
3778
|
stdio: "ignore",
|
|
3545
|
-
env:
|
|
3779
|
+
env: buildBridgeRuntimeEnv(),
|
|
3546
3780
|
windowsHide: process.platform === "win32",
|
|
3547
3781
|
},
|
|
3548
3782
|
);
|