@matelink/cli 2026.4.13 → 2026.4.15
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 +272 -9
- package/package.json +1 -1
package/bin/matecli.mjs
CHANGED
|
@@ -20,19 +20,37 @@ const NUMERIC_CODE_LENGTH = 4;
|
|
|
20
20
|
const GROUP_SIZE = 4;
|
|
21
21
|
const DEFAULT_RELAY_WORKER_WAIT_SECONDS = 600;
|
|
22
22
|
const DEFAULT_NETWORK_TIMEOUT_MS = 600000;
|
|
23
|
+
const BRIDGE_RETRY_DELAY_MS = 2000;
|
|
23
24
|
const DEFAULT_RELAY_URL = "http://43.134.64.199:8090";
|
|
24
25
|
const DEFAULT_GATEWAY_HOST = "127.0.0.1";
|
|
25
26
|
const DEFAULT_WEBHOOK_PATH = "/testnextim/webhook";
|
|
26
27
|
const DEFAULT_BIND_PATH = "/testnextim/bind";
|
|
27
28
|
const GATEWAY_RPC_CLI_TIMEOUT_MS = DEFAULT_NETWORK_TIMEOUT_MS;
|
|
29
|
+
const WORKSPACE_FILE_LIST_MAX = 2000;
|
|
30
|
+
const WORKSPACE_SKIP_DIRS = new Set([
|
|
31
|
+
".git",
|
|
32
|
+
"node_modules",
|
|
33
|
+
".next",
|
|
34
|
+
"dist",
|
|
35
|
+
"build",
|
|
36
|
+
".turbo",
|
|
37
|
+
".cache",
|
|
38
|
+
]);
|
|
28
39
|
const CLI_PACKAGE_NAME = "@matelink/cli";
|
|
29
40
|
const CLI_COMMAND_NAME = "matecli";
|
|
30
41
|
const SERVICE_LABEL = "com.matelink.matecli.bridge";
|
|
31
42
|
const SERVICE_UNIT_NAME = "matelink-matecli-bridge.service";
|
|
32
43
|
const SERVICE_TASK_NAME = "Matelink MateCLI Bridge";
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
|
|
44
|
+
// Mirror the WS bridge's operator scopes for local HTTP proxy calls as well.
|
|
45
|
+
// Some OpenClaw gateway builds require explicit read/write scopes and do not
|
|
46
|
+
// treat operator.admin as an umbrella permission for `/v1/responses`.
|
|
47
|
+
const DEFAULT_GATEWAY_SCOPES = [
|
|
48
|
+
"operator.admin",
|
|
49
|
+
"operator.read",
|
|
50
|
+
"operator.write",
|
|
51
|
+
"operator.approvals",
|
|
52
|
+
"operator.pairing",
|
|
53
|
+
].join(",");
|
|
36
54
|
const CLI_ENTRY = fileURLToPath(import.meta.url);
|
|
37
55
|
const CLI_LANGUAGE = detectCliLanguage();
|
|
38
56
|
const CLI_I18N = {
|
|
@@ -501,6 +519,208 @@ function readJsonFileIfExists(filePath) {
|
|
|
501
519
|
}
|
|
502
520
|
}
|
|
503
521
|
|
|
522
|
+
function readOpenClawConfigForRuntime() {
|
|
523
|
+
const configPath = resolveOpenClawConfigPath();
|
|
524
|
+
let content;
|
|
525
|
+
try {
|
|
526
|
+
content = fs.readFileSync(configPath, "utf8");
|
|
527
|
+
} catch {
|
|
528
|
+
throw new Error(t("config_read_failed", { path: configPath }));
|
|
529
|
+
}
|
|
530
|
+
try {
|
|
531
|
+
return JSON.parse(content);
|
|
532
|
+
} catch {
|
|
533
|
+
throw new Error(t("config_invalid_json", { path: configPath }));
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function resolveConfiguredWorkspaceRoot() {
|
|
538
|
+
const configPath = resolveOpenClawConfigPath();
|
|
539
|
+
const config = ensureObject(readOpenClawConfigForRuntime());
|
|
540
|
+
const workspaceRaw = String(config?.agents?.defaults?.workspace ?? "").trim();
|
|
541
|
+
const resolved = workspaceRaw
|
|
542
|
+
? (path.isAbsolute(workspaceRaw)
|
|
543
|
+
? workspaceRaw
|
|
544
|
+
: path.resolve(path.dirname(configPath), workspaceRaw))
|
|
545
|
+
: path.join(resolveOpenClawHome(), "workspace");
|
|
546
|
+
return path.resolve(resolved);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function normalizeWorkspaceRelativePath(rawName) {
|
|
550
|
+
const value = String(rawName ?? "").trim();
|
|
551
|
+
if (!value) {
|
|
552
|
+
throw new Error("workspace file name is required");
|
|
553
|
+
}
|
|
554
|
+
const normalized = value.replaceAll("\\", "/");
|
|
555
|
+
if (normalized.includes("\0") || /^[a-z]:\//i.test(normalized)) {
|
|
556
|
+
throw new Error("invalid workspace file path");
|
|
557
|
+
}
|
|
558
|
+
const relative = path.posix.normalize(normalized).replace(/^\/+/, "");
|
|
559
|
+
if (!relative || relative === "." || relative === ".." || relative.startsWith("../")) {
|
|
560
|
+
throw new Error("invalid workspace file path");
|
|
561
|
+
}
|
|
562
|
+
return relative;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function resolveWorkspaceFilePath(workspaceRoot, rawName) {
|
|
566
|
+
const relativePath = normalizeWorkspaceRelativePath(rawName);
|
|
567
|
+
const absolutePath = path.resolve(workspaceRoot, relativePath);
|
|
568
|
+
const relativeFromRoot = path.relative(workspaceRoot, absolutePath);
|
|
569
|
+
if (
|
|
570
|
+
!relativeFromRoot ||
|
|
571
|
+
relativeFromRoot === ".." ||
|
|
572
|
+
relativeFromRoot.startsWith(`..${path.sep}`) ||
|
|
573
|
+
path.isAbsolute(relativeFromRoot)
|
|
574
|
+
) {
|
|
575
|
+
throw new Error("workspace file path escapes workspace root");
|
|
576
|
+
}
|
|
577
|
+
return {
|
|
578
|
+
relativePath,
|
|
579
|
+
absolutePath,
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function buildWorkspaceFileMeta({
|
|
584
|
+
workspaceRoot,
|
|
585
|
+
relativePath,
|
|
586
|
+
absolutePath,
|
|
587
|
+
includeContent = false,
|
|
588
|
+
missing = false,
|
|
589
|
+
}) {
|
|
590
|
+
if (missing) {
|
|
591
|
+
return {
|
|
592
|
+
name: relativePath,
|
|
593
|
+
path: absolutePath,
|
|
594
|
+
missing: true,
|
|
595
|
+
size: null,
|
|
596
|
+
updatedAtMs: null,
|
|
597
|
+
content: null,
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
const stats = fs.statSync(absolutePath);
|
|
601
|
+
if (!stats.isFile()) {
|
|
602
|
+
throw new Error(`workspace path is not a file: ${relativePath}`);
|
|
603
|
+
}
|
|
604
|
+
return {
|
|
605
|
+
name: relativePath,
|
|
606
|
+
path: absolutePath,
|
|
607
|
+
missing: false,
|
|
608
|
+
size: stats.size,
|
|
609
|
+
updatedAtMs: Math.round(stats.mtimeMs),
|
|
610
|
+
content: includeContent ? fs.readFileSync(absolutePath, "utf8") : null,
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function listWorkspaceFiles(workspaceRoot) {
|
|
615
|
+
const files = [];
|
|
616
|
+
const walk = (relativeDir = "") => {
|
|
617
|
+
if (files.length >= WORKSPACE_FILE_LIST_MAX) {
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
const absoluteDir = relativeDir
|
|
621
|
+
? path.join(workspaceRoot, relativeDir)
|
|
622
|
+
: workspaceRoot;
|
|
623
|
+
let entries = [];
|
|
624
|
+
try {
|
|
625
|
+
entries = fs.readdirSync(absoluteDir, { withFileTypes: true });
|
|
626
|
+
} catch {
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
630
|
+
for (const entry of entries) {
|
|
631
|
+
if (files.length >= WORKSPACE_FILE_LIST_MAX) {
|
|
632
|
+
break;
|
|
633
|
+
}
|
|
634
|
+
if (entry.name === "." || entry.name === ".." || entry.isSymbolicLink()) {
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
const relativePath = relativeDir
|
|
638
|
+
? path.posix.join(relativeDir.replaceAll("\\", "/"), entry.name)
|
|
639
|
+
: entry.name;
|
|
640
|
+
const absolutePath = path.join(absoluteDir, entry.name);
|
|
641
|
+
if (entry.isDirectory()) {
|
|
642
|
+
if (WORKSPACE_SKIP_DIRS.has(entry.name)) {
|
|
643
|
+
continue;
|
|
644
|
+
}
|
|
645
|
+
walk(relativePath);
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
648
|
+
if (!entry.isFile()) {
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
try {
|
|
652
|
+
files.push(buildWorkspaceFileMeta({
|
|
653
|
+
workspaceRoot,
|
|
654
|
+
relativePath,
|
|
655
|
+
absolutePath,
|
|
656
|
+
}));
|
|
657
|
+
} catch {
|
|
658
|
+
// Ignore transient file stat/read issues.
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
walk("");
|
|
663
|
+
return files;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
async function callWorkspaceRpcLocal({
|
|
667
|
+
method,
|
|
668
|
+
params,
|
|
669
|
+
}) {
|
|
670
|
+
const workspaceRoot = resolveConfiguredWorkspaceRoot();
|
|
671
|
+
if (method === "agents.files.list") {
|
|
672
|
+
return {
|
|
673
|
+
ok: true,
|
|
674
|
+
workspace: workspaceRoot,
|
|
675
|
+
files: listWorkspaceFiles(workspaceRoot),
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if (method === "agents.files.get") {
|
|
680
|
+
const { relativePath, absolutePath } = resolveWorkspaceFilePath(workspaceRoot, params?.name);
|
|
681
|
+
if (!fs.existsSync(absolutePath)) {
|
|
682
|
+
return {
|
|
683
|
+
ok: true,
|
|
684
|
+
workspace: workspaceRoot,
|
|
685
|
+
file: buildWorkspaceFileMeta({
|
|
686
|
+
workspaceRoot,
|
|
687
|
+
relativePath,
|
|
688
|
+
absolutePath,
|
|
689
|
+
missing: true,
|
|
690
|
+
}),
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
return {
|
|
694
|
+
ok: true,
|
|
695
|
+
workspace: workspaceRoot,
|
|
696
|
+
file: buildWorkspaceFileMeta({
|
|
697
|
+
workspaceRoot,
|
|
698
|
+
relativePath,
|
|
699
|
+
absolutePath,
|
|
700
|
+
includeContent: true,
|
|
701
|
+
}),
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
if (method === "agents.files.set") {
|
|
706
|
+
const { relativePath, absolutePath } = resolveWorkspaceFilePath(workspaceRoot, params?.name);
|
|
707
|
+
fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
|
|
708
|
+
fs.writeFileSync(absolutePath, String(params?.content ?? ""), "utf8");
|
|
709
|
+
return {
|
|
710
|
+
ok: true,
|
|
711
|
+
workspace: workspaceRoot,
|
|
712
|
+
file: buildWorkspaceFileMeta({
|
|
713
|
+
workspaceRoot,
|
|
714
|
+
relativePath,
|
|
715
|
+
absolutePath,
|
|
716
|
+
includeContent: true,
|
|
717
|
+
}),
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
|
|
504
724
|
function commandToString(command, args) {
|
|
505
725
|
return [command, ...args]
|
|
506
726
|
.map((part) => {
|
|
@@ -991,6 +1211,27 @@ async function fetchWithTimeout(url, options = {}, timeoutMs = DEFAULT_NETWORK_T
|
|
|
991
1211
|
}
|
|
992
1212
|
}
|
|
993
1213
|
|
|
1214
|
+
function isTransientNetworkError(error) {
|
|
1215
|
+
const message = String(error instanceof Error ? error.message : error).toLowerCase();
|
|
1216
|
+
return (
|
|
1217
|
+
message.includes("timeout") ||
|
|
1218
|
+
message.includes("fetch failed") ||
|
|
1219
|
+
message.includes("terminated") ||
|
|
1220
|
+
message.includes("socket") ||
|
|
1221
|
+
message.includes("econnreset") ||
|
|
1222
|
+
message.includes("econnrefused") ||
|
|
1223
|
+
message.includes("ehostunreach") ||
|
|
1224
|
+
message.includes("enotfound") ||
|
|
1225
|
+
message.includes("etimedout") ||
|
|
1226
|
+
message.includes("epipe") ||
|
|
1227
|
+
message.includes("network")
|
|
1228
|
+
);
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
function sleep(ms) {
|
|
1232
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1233
|
+
}
|
|
1234
|
+
|
|
994
1235
|
function normalizeGatewayIdRaw(value) {
|
|
995
1236
|
return String(value ?? "")
|
|
996
1237
|
.trim()
|
|
@@ -2709,6 +2950,14 @@ async function callGatewayRpcLocal({
|
|
|
2709
2950
|
};
|
|
2710
2951
|
}
|
|
2711
2952
|
|
|
2953
|
+
if (
|
|
2954
|
+
method === "agents.files.list" ||
|
|
2955
|
+
method === "agents.files.get" ||
|
|
2956
|
+
method === "agents.files.set"
|
|
2957
|
+
) {
|
|
2958
|
+
return callWorkspaceRpcLocal({ method, params });
|
|
2959
|
+
}
|
|
2960
|
+
|
|
2712
2961
|
// Prefer CLI first for broad compatibility, but fall back to the persistent
|
|
2713
2962
|
// WS client when the CLI gateway call hits transient websocket closures.
|
|
2714
2963
|
try {
|
|
@@ -3104,12 +3353,26 @@ async function runRelayBridge({
|
|
|
3104
3353
|
const activeRequests = new Set();
|
|
3105
3354
|
|
|
3106
3355
|
while (true) {
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3356
|
+
let request;
|
|
3357
|
+
try {
|
|
3358
|
+
request = await readRelayNextGatewayRequest({
|
|
3359
|
+
relayUrl,
|
|
3360
|
+
gatewayId,
|
|
3361
|
+
gatewayToken,
|
|
3362
|
+
waitSeconds: DEFAULT_RELAY_WORKER_WAIT_SECONDS,
|
|
3363
|
+
});
|
|
3364
|
+
} catch (error) {
|
|
3365
|
+
if (!isTransientNetworkError(error)) {
|
|
3366
|
+
throw error;
|
|
3367
|
+
}
|
|
3368
|
+
if (!json) {
|
|
3369
|
+
console.warn(
|
|
3370
|
+
`[matecli] relay poll failed: ${String(error instanceof Error ? error.message : error)}; retrying in ${Math.round(BRIDGE_RETRY_DELAY_MS / 1000)}s`,
|
|
3371
|
+
);
|
|
3372
|
+
}
|
|
3373
|
+
await sleep(BRIDGE_RETRY_DELAY_MS);
|
|
3374
|
+
continue;
|
|
3375
|
+
}
|
|
3113
3376
|
if (!request) {
|
|
3114
3377
|
continue;
|
|
3115
3378
|
}
|