@matelink/cli 2026.4.12 → 2026.4.14
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 +260 -6
- package/package.json +1 -1
package/bin/matecli.mjs
CHANGED
|
@@ -25,14 +25,31 @@ const DEFAULT_GATEWAY_HOST = "127.0.0.1";
|
|
|
25
25
|
const DEFAULT_WEBHOOK_PATH = "/testnextim/webhook";
|
|
26
26
|
const DEFAULT_BIND_PATH = "/testnextim/bind";
|
|
27
27
|
const GATEWAY_RPC_CLI_TIMEOUT_MS = DEFAULT_NETWORK_TIMEOUT_MS;
|
|
28
|
+
const WORKSPACE_FILE_LIST_MAX = 2000;
|
|
29
|
+
const WORKSPACE_SKIP_DIRS = new Set([
|
|
30
|
+
".git",
|
|
31
|
+
"node_modules",
|
|
32
|
+
".next",
|
|
33
|
+
"dist",
|
|
34
|
+
"build",
|
|
35
|
+
".turbo",
|
|
36
|
+
".cache",
|
|
37
|
+
]);
|
|
28
38
|
const CLI_PACKAGE_NAME = "@matelink/cli";
|
|
29
39
|
const CLI_COMMAND_NAME = "matecli";
|
|
30
40
|
const SERVICE_LABEL = "com.matelink.matecli.bridge";
|
|
31
41
|
const SERVICE_UNIT_NAME = "matelink-matecli-bridge.service";
|
|
32
42
|
const SERVICE_TASK_NAME = "Matelink MateCLI Bridge";
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
|
|
43
|
+
// Mirror the WS bridge's operator scopes for local HTTP proxy calls as well.
|
|
44
|
+
// Some OpenClaw gateway builds require explicit read/write scopes and do not
|
|
45
|
+
// treat operator.admin as an umbrella permission for `/v1/responses`.
|
|
46
|
+
const DEFAULT_GATEWAY_SCOPES = [
|
|
47
|
+
"operator.admin",
|
|
48
|
+
"operator.read",
|
|
49
|
+
"operator.write",
|
|
50
|
+
"operator.approvals",
|
|
51
|
+
"operator.pairing",
|
|
52
|
+
].join(",");
|
|
36
53
|
const CLI_ENTRY = fileURLToPath(import.meta.url);
|
|
37
54
|
const CLI_LANGUAGE = detectCliLanguage();
|
|
38
55
|
const CLI_I18N = {
|
|
@@ -501,6 +518,208 @@ function readJsonFileIfExists(filePath) {
|
|
|
501
518
|
}
|
|
502
519
|
}
|
|
503
520
|
|
|
521
|
+
function readOpenClawConfigForRuntime() {
|
|
522
|
+
const configPath = resolveOpenClawConfigPath();
|
|
523
|
+
let content;
|
|
524
|
+
try {
|
|
525
|
+
content = fs.readFileSync(configPath, "utf8");
|
|
526
|
+
} catch {
|
|
527
|
+
throw new Error(t("config_read_failed", { path: configPath }));
|
|
528
|
+
}
|
|
529
|
+
try {
|
|
530
|
+
return JSON.parse(content);
|
|
531
|
+
} catch {
|
|
532
|
+
throw new Error(t("config_invalid_json", { path: configPath }));
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function resolveConfiguredWorkspaceRoot() {
|
|
537
|
+
const configPath = resolveOpenClawConfigPath();
|
|
538
|
+
const config = ensureObject(readOpenClawConfigForRuntime());
|
|
539
|
+
const workspaceRaw = String(config?.agents?.defaults?.workspace ?? "").trim();
|
|
540
|
+
const resolved = workspaceRaw
|
|
541
|
+
? (path.isAbsolute(workspaceRaw)
|
|
542
|
+
? workspaceRaw
|
|
543
|
+
: path.resolve(path.dirname(configPath), workspaceRaw))
|
|
544
|
+
: path.join(resolveOpenClawHome(), "workspace");
|
|
545
|
+
return path.resolve(resolved);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function normalizeWorkspaceRelativePath(rawName) {
|
|
549
|
+
const value = String(rawName ?? "").trim();
|
|
550
|
+
if (!value) {
|
|
551
|
+
throw new Error("workspace file name is required");
|
|
552
|
+
}
|
|
553
|
+
const normalized = value.replaceAll("\\", "/");
|
|
554
|
+
if (normalized.includes("\0") || /^[a-z]:\//i.test(normalized)) {
|
|
555
|
+
throw new Error("invalid workspace file path");
|
|
556
|
+
}
|
|
557
|
+
const relative = path.posix.normalize(normalized).replace(/^\/+/, "");
|
|
558
|
+
if (!relative || relative === "." || relative === ".." || relative.startsWith("../")) {
|
|
559
|
+
throw new Error("invalid workspace file path");
|
|
560
|
+
}
|
|
561
|
+
return relative;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function resolveWorkspaceFilePath(workspaceRoot, rawName) {
|
|
565
|
+
const relativePath = normalizeWorkspaceRelativePath(rawName);
|
|
566
|
+
const absolutePath = path.resolve(workspaceRoot, relativePath);
|
|
567
|
+
const relativeFromRoot = path.relative(workspaceRoot, absolutePath);
|
|
568
|
+
if (
|
|
569
|
+
!relativeFromRoot ||
|
|
570
|
+
relativeFromRoot === ".." ||
|
|
571
|
+
relativeFromRoot.startsWith(`..${path.sep}`) ||
|
|
572
|
+
path.isAbsolute(relativeFromRoot)
|
|
573
|
+
) {
|
|
574
|
+
throw new Error("workspace file path escapes workspace root");
|
|
575
|
+
}
|
|
576
|
+
return {
|
|
577
|
+
relativePath,
|
|
578
|
+
absolutePath,
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function buildWorkspaceFileMeta({
|
|
583
|
+
workspaceRoot,
|
|
584
|
+
relativePath,
|
|
585
|
+
absolutePath,
|
|
586
|
+
includeContent = false,
|
|
587
|
+
missing = false,
|
|
588
|
+
}) {
|
|
589
|
+
if (missing) {
|
|
590
|
+
return {
|
|
591
|
+
name: relativePath,
|
|
592
|
+
path: absolutePath,
|
|
593
|
+
missing: true,
|
|
594
|
+
size: null,
|
|
595
|
+
updatedAtMs: null,
|
|
596
|
+
content: null,
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
const stats = fs.statSync(absolutePath);
|
|
600
|
+
if (!stats.isFile()) {
|
|
601
|
+
throw new Error(`workspace path is not a file: ${relativePath}`);
|
|
602
|
+
}
|
|
603
|
+
return {
|
|
604
|
+
name: relativePath,
|
|
605
|
+
path: absolutePath,
|
|
606
|
+
missing: false,
|
|
607
|
+
size: stats.size,
|
|
608
|
+
updatedAtMs: Math.round(stats.mtimeMs),
|
|
609
|
+
content: includeContent ? fs.readFileSync(absolutePath, "utf8") : null,
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function listWorkspaceFiles(workspaceRoot) {
|
|
614
|
+
const files = [];
|
|
615
|
+
const walk = (relativeDir = "") => {
|
|
616
|
+
if (files.length >= WORKSPACE_FILE_LIST_MAX) {
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
const absoluteDir = relativeDir
|
|
620
|
+
? path.join(workspaceRoot, relativeDir)
|
|
621
|
+
: workspaceRoot;
|
|
622
|
+
let entries = [];
|
|
623
|
+
try {
|
|
624
|
+
entries = fs.readdirSync(absoluteDir, { withFileTypes: true });
|
|
625
|
+
} catch {
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
629
|
+
for (const entry of entries) {
|
|
630
|
+
if (files.length >= WORKSPACE_FILE_LIST_MAX) {
|
|
631
|
+
break;
|
|
632
|
+
}
|
|
633
|
+
if (entry.name === "." || entry.name === ".." || entry.isSymbolicLink()) {
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
const relativePath = relativeDir
|
|
637
|
+
? path.posix.join(relativeDir.replaceAll("\\", "/"), entry.name)
|
|
638
|
+
: entry.name;
|
|
639
|
+
const absolutePath = path.join(absoluteDir, entry.name);
|
|
640
|
+
if (entry.isDirectory()) {
|
|
641
|
+
if (WORKSPACE_SKIP_DIRS.has(entry.name)) {
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
walk(relativePath);
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
if (!entry.isFile()) {
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
try {
|
|
651
|
+
files.push(buildWorkspaceFileMeta({
|
|
652
|
+
workspaceRoot,
|
|
653
|
+
relativePath,
|
|
654
|
+
absolutePath,
|
|
655
|
+
}));
|
|
656
|
+
} catch {
|
|
657
|
+
// Ignore transient file stat/read issues.
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
walk("");
|
|
662
|
+
return files;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
async function callWorkspaceRpcLocal({
|
|
666
|
+
method,
|
|
667
|
+
params,
|
|
668
|
+
}) {
|
|
669
|
+
const workspaceRoot = resolveConfiguredWorkspaceRoot();
|
|
670
|
+
if (method === "agents.files.list") {
|
|
671
|
+
return {
|
|
672
|
+
ok: true,
|
|
673
|
+
workspace: workspaceRoot,
|
|
674
|
+
files: listWorkspaceFiles(workspaceRoot),
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
if (method === "agents.files.get") {
|
|
679
|
+
const { relativePath, absolutePath } = resolveWorkspaceFilePath(workspaceRoot, params?.name);
|
|
680
|
+
if (!fs.existsSync(absolutePath)) {
|
|
681
|
+
return {
|
|
682
|
+
ok: true,
|
|
683
|
+
workspace: workspaceRoot,
|
|
684
|
+
file: buildWorkspaceFileMeta({
|
|
685
|
+
workspaceRoot,
|
|
686
|
+
relativePath,
|
|
687
|
+
absolutePath,
|
|
688
|
+
missing: true,
|
|
689
|
+
}),
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
return {
|
|
693
|
+
ok: true,
|
|
694
|
+
workspace: workspaceRoot,
|
|
695
|
+
file: buildWorkspaceFileMeta({
|
|
696
|
+
workspaceRoot,
|
|
697
|
+
relativePath,
|
|
698
|
+
absolutePath,
|
|
699
|
+
includeContent: true,
|
|
700
|
+
}),
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
if (method === "agents.files.set") {
|
|
705
|
+
const { relativePath, absolutePath } = resolveWorkspaceFilePath(workspaceRoot, params?.name);
|
|
706
|
+
fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
|
|
707
|
+
fs.writeFileSync(absolutePath, String(params?.content ?? ""), "utf8");
|
|
708
|
+
return {
|
|
709
|
+
ok: true,
|
|
710
|
+
workspace: workspaceRoot,
|
|
711
|
+
file: buildWorkspaceFileMeta({
|
|
712
|
+
workspaceRoot,
|
|
713
|
+
relativePath,
|
|
714
|
+
absolutePath,
|
|
715
|
+
includeContent: true,
|
|
716
|
+
}),
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
return null;
|
|
721
|
+
}
|
|
722
|
+
|
|
504
723
|
function commandToString(command, args) {
|
|
505
724
|
return [command, ...args]
|
|
506
725
|
.map((part) => {
|
|
@@ -2709,9 +2928,42 @@ async function callGatewayRpcLocal({
|
|
|
2709
2928
|
};
|
|
2710
2929
|
}
|
|
2711
2930
|
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2931
|
+
if (
|
|
2932
|
+
method === "agents.files.list" ||
|
|
2933
|
+
method === "agents.files.get" ||
|
|
2934
|
+
method === "agents.files.set"
|
|
2935
|
+
) {
|
|
2936
|
+
return callWorkspaceRpcLocal({ method, params });
|
|
2937
|
+
}
|
|
2938
|
+
|
|
2939
|
+
// Prefer CLI first for broad compatibility, but fall back to the persistent
|
|
2940
|
+
// WS client when the CLI gateway call hits transient websocket closures.
|
|
2941
|
+
try {
|
|
2942
|
+
return await callGatewayRpcLocalViaCli({
|
|
2943
|
+
gatewayBaseUrl,
|
|
2944
|
+
gatewayAuthToken,
|
|
2945
|
+
method,
|
|
2946
|
+
params,
|
|
2947
|
+
});
|
|
2948
|
+
} catch (error) {
|
|
2949
|
+
if (!shouldFallbackGatewayRpcViaWs(error)) {
|
|
2950
|
+
throw error;
|
|
2951
|
+
}
|
|
2952
|
+
const client = getOrCreateGatewayWsClient({ gatewayBaseUrl, gatewayAuthToken });
|
|
2953
|
+
return client.call(method, params ?? {});
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
|
|
2957
|
+
function shouldFallbackGatewayRpcViaWs(error) {
|
|
2958
|
+
const message = String(error instanceof Error ? error.message : error).toLowerCase();
|
|
2959
|
+
return (
|
|
2960
|
+
message.includes("gateway closed") ||
|
|
2961
|
+
message.includes("abnormal closure") ||
|
|
2962
|
+
message.includes("(1006") ||
|
|
2963
|
+
message.includes("no close reason") ||
|
|
2964
|
+
message.includes("gateway ws error") ||
|
|
2965
|
+
message.includes("gateway disconnected")
|
|
2966
|
+
);
|
|
2715
2967
|
}
|
|
2716
2968
|
|
|
2717
2969
|
async function callGatewayRpcLocalViaCli({
|
|
@@ -2727,6 +2979,8 @@ async function callGatewayRpcLocalViaCli({
|
|
|
2727
2979
|
method,
|
|
2728
2980
|
"--url",
|
|
2729
2981
|
wsUrl,
|
|
2982
|
+
"--timeout",
|
|
2983
|
+
String(GATEWAY_RPC_CLI_TIMEOUT_MS),
|
|
2730
2984
|
"--params",
|
|
2731
2985
|
JSON.stringify(params ?? {}),
|
|
2732
2986
|
"--json",
|