squad-openclaw 2026.2.1903 → 2026.2.1905
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/dist/index.d.ts +26 -1
- package/dist/index.js +252 -34
- package/openclaw.plugin.json +1 -1
- package/package.json +6 -6
package/dist/index.d.ts
CHANGED
|
@@ -3,9 +3,34 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Provides:
|
|
5
5
|
* - In-memory entity registry with filesystem watching (entity_list, entity_search, entity_sync)
|
|
6
|
-
* - Filesystem tools for remote clients (fs_read, fs_write, fs_list, fs_delete)
|
|
6
|
+
* - Filesystem tools for remote clients (fs_read, fs_write, fs_list, fs_delete, fs_rename)
|
|
7
|
+
* - Restricted SQL query tool (sql_query) — sqlite3 only, squad-ceo-data/ only
|
|
7
8
|
* - Version check and self-update gateway methods (squad.version.*)
|
|
8
9
|
* - Cloud relay client for remote browser access (relay-client)
|
|
10
|
+
*
|
|
11
|
+
* ┌──────────────────────────────────────────────────────────────────────────┐
|
|
12
|
+
* │ SECURITY POLICY — Credential Protection │
|
|
13
|
+
* │ │
|
|
14
|
+
* │ This plugin enforces hard-coded security rules in filesystem.ts: │
|
|
15
|
+
* │ │
|
|
16
|
+
* │ BLOCKED directories (no read, write, list, delete, or rename): │
|
|
17
|
+
* │ • ~/.openclaw/credentials/ │
|
|
18
|
+
* │ • ~/.openclaw/devices/ │
|
|
19
|
+
* │ • ~/.openclaw/identity/ │
|
|
20
|
+
* │ │
|
|
21
|
+
* │ REDACTED on read (sensitive fields replaced with "[REDACTED]"): │
|
|
22
|
+
* │ • ~/.openclaw/openclaw.json → channel.*.botToken │
|
|
23
|
+
* │ • ~/.openclaw/openclaw.json → gateway.auth.* │
|
|
24
|
+
* │ • squad-ceo-data/squad-relay.json → deviceKeys.privateKeyPem │
|
|
25
|
+
* │ │
|
|
26
|
+
* │ WRITE-PROTECTED (no writes, deletes, or renames): │
|
|
27
|
+
* │ • ~/.openclaw/openclaw.json │
|
|
28
|
+
* │ • squad-ceo-data/squad-relay.json │
|
|
29
|
+
* │ • All blocked directories above │
|
|
30
|
+
* │ │
|
|
31
|
+
* │ The bundle is NOT minified to allow security auditing of the │
|
|
32
|
+
* │ distributed code. See tsup.config.ts for build configuration. │
|
|
33
|
+
* └──────────────────────────────────────────────────────────────────────────┘
|
|
9
34
|
*/
|
|
10
35
|
declare function squadAppPlugin(api: any): void;
|
|
11
36
|
|
package/dist/index.js
CHANGED
|
@@ -618,9 +618,78 @@ function registerEntityTools(api, onFsChange) {
|
|
|
618
618
|
// src/filesystem.ts
|
|
619
619
|
import fs3 from "fs";
|
|
620
620
|
import path3 from "path";
|
|
621
|
+
var HOME_DIR = process.env.HOME ?? "/root";
|
|
622
|
+
var OPENCLAW_DIR = path3.join(HOME_DIR, ".openclaw");
|
|
623
|
+
var SENSITIVE_BLOCKED_DIRS = [
|
|
624
|
+
path3.join(OPENCLAW_DIR, "credentials"),
|
|
625
|
+
path3.join(OPENCLAW_DIR, "devices"),
|
|
626
|
+
path3.join(OPENCLAW_DIR, "identity")
|
|
627
|
+
];
|
|
628
|
+
var SENSITIVE_BLOCKED_FILES = [
|
|
629
|
+
path3.join(OPENCLAW_DIR, "squad-ceo-data", "squad-relay.json")
|
|
630
|
+
];
|
|
631
|
+
function isSensitivePath(resolvedPath) {
|
|
632
|
+
for (const blocked of SENSITIVE_BLOCKED_DIRS) {
|
|
633
|
+
if (resolvedPath === blocked || resolvedPath.startsWith(blocked + path3.sep)) {
|
|
634
|
+
return true;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
for (const blocked of SENSITIVE_BLOCKED_FILES) {
|
|
638
|
+
if (resolvedPath === blocked) {
|
|
639
|
+
return true;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return false;
|
|
643
|
+
}
|
|
644
|
+
var OPENCLAW_JSON_FILENAME = "openclaw.json";
|
|
645
|
+
function redactOpenclawJson(rawContent) {
|
|
646
|
+
let config;
|
|
647
|
+
try {
|
|
648
|
+
config = JSON.parse(rawContent);
|
|
649
|
+
} catch {
|
|
650
|
+
return rawContent;
|
|
651
|
+
}
|
|
652
|
+
let redactedCount = 0;
|
|
653
|
+
const channels = config.channels;
|
|
654
|
+
if (channels && typeof channels === "object") {
|
|
655
|
+
for (const channelKey of Object.keys(channels)) {
|
|
656
|
+
const channel = channels[channelKey];
|
|
657
|
+
if (channel && typeof channel === "object" && "botToken" in channel) {
|
|
658
|
+
channel.botToken = "[REDACTED]";
|
|
659
|
+
redactedCount++;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
const gateway = config.gateway;
|
|
664
|
+
if (gateway && typeof gateway === "object") {
|
|
665
|
+
if (gateway.auth && typeof gateway.auth === "object") {
|
|
666
|
+
const auth = gateway.auth;
|
|
667
|
+
for (const key of Object.keys(auth)) {
|
|
668
|
+
auth[key] = "[REDACTED]";
|
|
669
|
+
redactedCount++;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
if ("token" in gateway) {
|
|
673
|
+
gateway.token = "[REDACTED]";
|
|
674
|
+
redactedCount++;
|
|
675
|
+
}
|
|
676
|
+
const remote = gateway.remote;
|
|
677
|
+
if (remote && typeof remote === "object" && "token" in remote) {
|
|
678
|
+
remote.token = "[REDACTED]";
|
|
679
|
+
redactedCount++;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
if (redactedCount > 0) {
|
|
683
|
+
console.log(`[security] Redacted ${redactedCount} sensitive field(s) from openclaw.json before returning to client`);
|
|
684
|
+
}
|
|
685
|
+
return JSON.stringify(config, null, 2);
|
|
686
|
+
}
|
|
687
|
+
function isOpenclawJson(resolvedPath) {
|
|
688
|
+
return path3.basename(resolvedPath) === OPENCLAW_JSON_FILENAME && resolvedPath.startsWith(OPENCLAW_DIR);
|
|
689
|
+
}
|
|
621
690
|
function expandHome(p) {
|
|
622
691
|
if (p.startsWith("~/") || p === "~") {
|
|
623
|
-
return path3.join(
|
|
692
|
+
return path3.join(HOME_DIR, p.slice(1));
|
|
624
693
|
}
|
|
625
694
|
return p;
|
|
626
695
|
}
|
|
@@ -636,6 +705,24 @@ function validatePath(p, allowedRoots) {
|
|
|
636
705
|
}
|
|
637
706
|
return resolved;
|
|
638
707
|
}
|
|
708
|
+
function validateAndBlockSensitive(p, allowedRoots) {
|
|
709
|
+
const resolved = validatePath(p, allowedRoots);
|
|
710
|
+
if (isSensitivePath(resolved)) {
|
|
711
|
+
throw new Error(
|
|
712
|
+
`Access denied: path "${p}" is inside a protected directory (credentials/devices/identity)`
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
return resolved;
|
|
716
|
+
}
|
|
717
|
+
function validateWritePath(p, allowedRoots) {
|
|
718
|
+
const resolved = validateAndBlockSensitive(p, allowedRoots);
|
|
719
|
+
if (isOpenclawJson(resolved)) {
|
|
720
|
+
throw new Error(
|
|
721
|
+
`Write denied: "${p}" is a protected configuration file (openclaw.json)`
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
return resolved;
|
|
725
|
+
}
|
|
639
726
|
function ok(data) {
|
|
640
727
|
return {
|
|
641
728
|
content: [{ type: "text", text: JSON.stringify(data) }]
|
|
@@ -674,12 +761,20 @@ function listDir(dirPath, opts) {
|
|
|
674
761
|
}
|
|
675
762
|
return results;
|
|
676
763
|
}
|
|
764
|
+
function filterSensitiveEntries(entries) {
|
|
765
|
+
return entries.filter((entry) => !isSensitivePath(entry.path)).map((entry) => {
|
|
766
|
+
if (entry.children) {
|
|
767
|
+
return { ...entry, children: filterSensitiveEntries(entry.children) };
|
|
768
|
+
}
|
|
769
|
+
return entry;
|
|
770
|
+
});
|
|
771
|
+
}
|
|
677
772
|
function registerFilesystemTools(api) {
|
|
678
773
|
const allowedRoots = api.pluginConfig?.["fs.allowedRoots"] ?? [];
|
|
679
774
|
api.registerTool({
|
|
680
775
|
name: "fs_read",
|
|
681
776
|
label: "Read File",
|
|
682
|
-
description: "Read a file from the server filesystem. Returns the file contents as text. Supports ~ for home directory expansion.",
|
|
777
|
+
description: "Read a file from the server filesystem. Returns the file contents as text. Supports ~ for home directory expansion. Sensitive directories (credentials, devices, identity) are blocked. Config files are returned with auth tokens redacted.",
|
|
683
778
|
parameters: {
|
|
684
779
|
type: "object",
|
|
685
780
|
properties: {
|
|
@@ -697,10 +792,13 @@ function registerFilesystemTools(api) {
|
|
|
697
792
|
},
|
|
698
793
|
async execute(_id, params) {
|
|
699
794
|
try {
|
|
700
|
-
const filePath =
|
|
795
|
+
const filePath = validateAndBlockSensitive(params.path, allowedRoots);
|
|
701
796
|
const encoding = params.encoding ?? "utf-8";
|
|
702
|
-
|
|
797
|
+
let content = fs3.readFileSync(filePath, encoding);
|
|
703
798
|
const stat = fs3.statSync(filePath);
|
|
799
|
+
if (isOpenclawJson(filePath) && encoding === "utf-8") {
|
|
800
|
+
content = redactOpenclawJson(content);
|
|
801
|
+
}
|
|
704
802
|
return ok({
|
|
705
803
|
path: filePath,
|
|
706
804
|
content,
|
|
@@ -716,7 +814,7 @@ function registerFilesystemTools(api) {
|
|
|
716
814
|
api.registerTool({
|
|
717
815
|
name: "fs_write",
|
|
718
816
|
label: "Write File",
|
|
719
|
-
description: "Write content to a file on the server filesystem. Creates parent directories if they don't exist. Supports ~ for home directory expansion.",
|
|
817
|
+
description: "Write content to a file on the server filesystem. Creates parent directories if they don't exist. Supports ~ for home directory expansion. Writes to protected directories (credentials, devices, identity) and config files (openclaw.json) are denied.",
|
|
720
818
|
parameters: {
|
|
721
819
|
type: "object",
|
|
722
820
|
properties: {
|
|
@@ -742,7 +840,7 @@ function registerFilesystemTools(api) {
|
|
|
742
840
|
},
|
|
743
841
|
async execute(_id, params) {
|
|
744
842
|
try {
|
|
745
|
-
const filePath =
|
|
843
|
+
const filePath = validateWritePath(params.path, allowedRoots);
|
|
746
844
|
const content = params.content;
|
|
747
845
|
const encoding = params.encoding ?? "utf-8";
|
|
748
846
|
const mkdir = params.mkdir !== false;
|
|
@@ -765,7 +863,7 @@ function registerFilesystemTools(api) {
|
|
|
765
863
|
api.registerTool({
|
|
766
864
|
name: "fs_list",
|
|
767
865
|
label: "List Directory",
|
|
768
|
-
description: "List contents of a directory on the server filesystem. Returns file metadata including name, type, size, and modification time. Supports ~ for home directory expansion.",
|
|
866
|
+
description: "List contents of a directory on the server filesystem. Returns file metadata including name, type, size, and modification time. Supports ~ for home directory expansion. Protected directories (credentials, devices, identity) are excluded from results.",
|
|
769
867
|
parameters: {
|
|
770
868
|
type: "object",
|
|
771
869
|
properties: {
|
|
@@ -786,10 +884,11 @@ function registerFilesystemTools(api) {
|
|
|
786
884
|
},
|
|
787
885
|
async execute(_id, params) {
|
|
788
886
|
try {
|
|
789
|
-
const dirPath =
|
|
887
|
+
const dirPath = validateAndBlockSensitive(params.path, allowedRoots);
|
|
790
888
|
const recursive = params.recursive === true;
|
|
791
889
|
const includeHidden = params.includeHidden === true;
|
|
792
|
-
|
|
890
|
+
let entries = listDir(dirPath, { recursive, includeHidden, depth: 0, maxDepth: 3 });
|
|
891
|
+
entries = filterSensitiveEntries(entries);
|
|
793
892
|
return ok({
|
|
794
893
|
path: dirPath,
|
|
795
894
|
count: entries.length,
|
|
@@ -804,7 +903,7 @@ function registerFilesystemTools(api) {
|
|
|
804
903
|
api.registerTool({
|
|
805
904
|
name: "fs_mkdir",
|
|
806
905
|
label: "Create Directory",
|
|
807
|
-
description: "Create a directory on the server filesystem. Creates parent directories as needed. Supports ~ for home directory expansion.",
|
|
906
|
+
description: "Create a directory on the server filesystem. Creates parent directories as needed. Supports ~ for home directory expansion. Cannot create directories inside protected paths (credentials, devices, identity).",
|
|
808
907
|
parameters: {
|
|
809
908
|
type: "object",
|
|
810
909
|
properties: {
|
|
@@ -817,7 +916,7 @@ function registerFilesystemTools(api) {
|
|
|
817
916
|
},
|
|
818
917
|
async execute(_id, params) {
|
|
819
918
|
try {
|
|
820
|
-
const targetPath =
|
|
919
|
+
const targetPath = validateWritePath(params.path, allowedRoots);
|
|
821
920
|
fs3.mkdirSync(targetPath, { recursive: true });
|
|
822
921
|
return ok({
|
|
823
922
|
path: targetPath,
|
|
@@ -829,10 +928,44 @@ function registerFilesystemTools(api) {
|
|
|
829
928
|
}
|
|
830
929
|
}
|
|
831
930
|
});
|
|
931
|
+
api.registerTool({
|
|
932
|
+
name: "fs_rename",
|
|
933
|
+
label: "Rename / Move",
|
|
934
|
+
description: "Rename or move a file or directory on the server filesystem. Supports ~ for home directory expansion. Cannot move files into or out of protected directories.",
|
|
935
|
+
parameters: {
|
|
936
|
+
type: "object",
|
|
937
|
+
properties: {
|
|
938
|
+
oldPath: {
|
|
939
|
+
type: "string",
|
|
940
|
+
description: "Current absolute or ~-prefixed path"
|
|
941
|
+
},
|
|
942
|
+
newPath: {
|
|
943
|
+
type: "string",
|
|
944
|
+
description: "New absolute or ~-prefixed path"
|
|
945
|
+
}
|
|
946
|
+
},
|
|
947
|
+
required: ["oldPath", "newPath"]
|
|
948
|
+
},
|
|
949
|
+
async execute(_id, params) {
|
|
950
|
+
try {
|
|
951
|
+
const resolvedOld = validateWritePath(params.oldPath, allowedRoots);
|
|
952
|
+
const resolvedNew = validateWritePath(params.newPath, allowedRoots);
|
|
953
|
+
fs3.renameSync(resolvedOld, resolvedNew);
|
|
954
|
+
return ok({
|
|
955
|
+
oldPath: resolvedOld,
|
|
956
|
+
newPath: resolvedNew,
|
|
957
|
+
renamed: true
|
|
958
|
+
});
|
|
959
|
+
} catch (e) {
|
|
960
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
961
|
+
return err(`fs_rename failed: ${msg}`);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
});
|
|
832
965
|
api.registerTool({
|
|
833
966
|
name: "fs_delete",
|
|
834
967
|
label: "Delete File or Directory",
|
|
835
|
-
description: "Delete a file or directory from the server filesystem. For directories, removes recursively. Supports ~ for home directory expansion.",
|
|
968
|
+
description: "Delete a file or directory from the server filesystem. For directories, removes recursively. Supports ~ for home directory expansion. Cannot delete protected directories or config files.",
|
|
836
969
|
parameters: {
|
|
837
970
|
type: "object",
|
|
838
971
|
properties: {
|
|
@@ -845,7 +978,7 @@ function registerFilesystemTools(api) {
|
|
|
845
978
|
},
|
|
846
979
|
async execute(_id, params) {
|
|
847
980
|
try {
|
|
848
|
-
const targetPath =
|
|
981
|
+
const targetPath = validateWritePath(params.path, allowedRoots);
|
|
849
982
|
const stat = fs3.statSync(targetPath);
|
|
850
983
|
const wasDirectory = stat.isDirectory();
|
|
851
984
|
if (wasDirectory) {
|
|
@@ -866,17 +999,101 @@ function registerFilesystemTools(api) {
|
|
|
866
999
|
});
|
|
867
1000
|
}
|
|
868
1001
|
|
|
1002
|
+
// src/sql.ts
|
|
1003
|
+
import { execFile } from "child_process";
|
|
1004
|
+
import path4 from "path";
|
|
1005
|
+
import fs4 from "fs";
|
|
1006
|
+
import { Type as T2 } from "@sinclair/typebox";
|
|
1007
|
+
var HOME_DIR2 = process.env.HOME ?? "/root";
|
|
1008
|
+
var ALLOWED_DATA_DIR = path4.join(HOME_DIR2, ".openclaw", "squad-ceo-data");
|
|
1009
|
+
function validateDbPath(dbPath) {
|
|
1010
|
+
let expanded = dbPath;
|
|
1011
|
+
if (expanded.startsWith("~/") || expanded === "~") {
|
|
1012
|
+
expanded = path4.join(HOME_DIR2, expanded.slice(1));
|
|
1013
|
+
}
|
|
1014
|
+
const resolved = path4.resolve(expanded);
|
|
1015
|
+
if (resolved !== ALLOWED_DATA_DIR && !resolved.startsWith(ALLOWED_DATA_DIR + path4.sep)) {
|
|
1016
|
+
throw new Error(
|
|
1017
|
+
`Access denied: database path must be within ~/.openclaw/squad-ceo-data/`
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
1020
|
+
try {
|
|
1021
|
+
const stat = fs4.statSync(resolved);
|
|
1022
|
+
if (!stat.isFile()) {
|
|
1023
|
+
throw new Error(`Not a file: ${dbPath}`);
|
|
1024
|
+
}
|
|
1025
|
+
} catch (e) {
|
|
1026
|
+
if (e.code === "ENOENT") {
|
|
1027
|
+
throw new Error(`Database file not found: ${dbPath}`);
|
|
1028
|
+
}
|
|
1029
|
+
throw e;
|
|
1030
|
+
}
|
|
1031
|
+
return resolved;
|
|
1032
|
+
}
|
|
1033
|
+
function runSqlite3(dbPath, args) {
|
|
1034
|
+
return new Promise((resolve, reject) => {
|
|
1035
|
+
execFile(
|
|
1036
|
+
"sqlite3",
|
|
1037
|
+
[dbPath, ...args],
|
|
1038
|
+
{ timeout: 3e4, maxBuffer: 10 * 1024 * 1024 },
|
|
1039
|
+
(error, stdout, stderr) => {
|
|
1040
|
+
if (error) {
|
|
1041
|
+
reject(new Error(stderr || error.message));
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
resolve(stdout);
|
|
1045
|
+
}
|
|
1046
|
+
);
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
function registerSqlTools(api) {
|
|
1050
|
+
api.registerTool({
|
|
1051
|
+
name: "sql_query",
|
|
1052
|
+
label: "SQL Query",
|
|
1053
|
+
description: "Execute a sqlite3 query on a database file within ~/.openclaw/squad-ceo-data/. Only sqlite3 is allowed \u2014 no arbitrary shell commands. Use jsonOutput: true for structured JSON results.",
|
|
1054
|
+
parameters: T2.Object({
|
|
1055
|
+
dbPath: T2.String({
|
|
1056
|
+
description: "Path to the SQLite database file (must be within ~/.openclaw/squad-ceo-data/)"
|
|
1057
|
+
}),
|
|
1058
|
+
query: T2.String({ description: "SQL query to execute" }),
|
|
1059
|
+
jsonOutput: T2.Optional(
|
|
1060
|
+
T2.Boolean({
|
|
1061
|
+
description: "Return results as JSON (sqlite3 -json flag)"
|
|
1062
|
+
})
|
|
1063
|
+
)
|
|
1064
|
+
}),
|
|
1065
|
+
async execute(_id, params) {
|
|
1066
|
+
try {
|
|
1067
|
+
const resolvedDb = validateDbPath(params.dbPath);
|
|
1068
|
+
const args = [];
|
|
1069
|
+
if (params.jsonOutput) args.push("-json");
|
|
1070
|
+
args.push(params.query);
|
|
1071
|
+
const output = await runSqlite3(resolvedDb, args);
|
|
1072
|
+
return {
|
|
1073
|
+
content: [{ type: "text", text: output }]
|
|
1074
|
+
};
|
|
1075
|
+
} catch (e) {
|
|
1076
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1077
|
+
return {
|
|
1078
|
+
content: [{ type: "text", text: JSON.stringify({ error: msg }) }],
|
|
1079
|
+
isError: true
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
|
|
869
1086
|
// src/version.ts
|
|
870
1087
|
import { execSync } from "child_process";
|
|
871
|
-
import
|
|
872
|
-
import
|
|
1088
|
+
import fs5 from "fs";
|
|
1089
|
+
import path5 from "path";
|
|
873
1090
|
import { fileURLToPath } from "url";
|
|
874
1091
|
var PACKAGE_NAME = "squad-openclaw";
|
|
875
1092
|
function getCurrentVersion() {
|
|
876
1093
|
const thisFile = fileURLToPath(import.meta.url);
|
|
877
|
-
const pkgPath =
|
|
1094
|
+
const pkgPath = path5.resolve(path5.dirname(thisFile), "..", "package.json");
|
|
878
1095
|
try {
|
|
879
|
-
const pkg = JSON.parse(
|
|
1096
|
+
const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
|
|
880
1097
|
return pkg.version ?? "0.0.0";
|
|
881
1098
|
} catch {
|
|
882
1099
|
return "0.0.0";
|
|
@@ -974,8 +1191,8 @@ function registerVersionMethods(api) {
|
|
|
974
1191
|
// src/relay-client.ts
|
|
975
1192
|
import { WebSocket as NodeWebSocket } from "ws";
|
|
976
1193
|
import crypto2 from "crypto";
|
|
977
|
-
import
|
|
978
|
-
import
|
|
1194
|
+
import fs6 from "fs";
|
|
1195
|
+
import path6 from "path";
|
|
979
1196
|
import os from "os";
|
|
980
1197
|
|
|
981
1198
|
// src/e2e-crypto.ts
|
|
@@ -1057,31 +1274,31 @@ var E2ECrypto = class {
|
|
|
1057
1274
|
|
|
1058
1275
|
// src/relay-client.ts
|
|
1059
1276
|
function readOperatorToken() {
|
|
1060
|
-
const stateDir = process.env.OPENCLAW_STATE_DIR ||
|
|
1061
|
-
const configPath =
|
|
1277
|
+
const stateDir = process.env.OPENCLAW_STATE_DIR || path6.join(os.homedir(), ".openclaw");
|
|
1278
|
+
const configPath = path6.join(stateDir, "openclaw.json");
|
|
1062
1279
|
try {
|
|
1063
|
-
const raw =
|
|
1280
|
+
const raw = fs6.readFileSync(configPath, "utf-8");
|
|
1064
1281
|
const config = JSON.parse(raw);
|
|
1065
1282
|
return config?.gateway?.auth?.token ?? config?.gateway?.remote?.token ?? config?.gateway?.token ?? null;
|
|
1066
1283
|
} catch {
|
|
1067
1284
|
return null;
|
|
1068
1285
|
}
|
|
1069
1286
|
}
|
|
1070
|
-
var RELAY_DATA_DIR =
|
|
1071
|
-
var RELAY_STATE_PATH =
|
|
1287
|
+
var RELAY_DATA_DIR = path6.join(os.homedir(), ".openclaw", "squad-ceo-data");
|
|
1288
|
+
var RELAY_STATE_PATH = path6.join(RELAY_DATA_DIR, "squad-relay.json");
|
|
1072
1289
|
function readRelayState() {
|
|
1073
1290
|
try {
|
|
1074
|
-
const raw =
|
|
1291
|
+
const raw = fs6.readFileSync(RELAY_STATE_PATH, "utf-8");
|
|
1075
1292
|
return JSON.parse(raw);
|
|
1076
1293
|
} catch {
|
|
1077
1294
|
return {};
|
|
1078
1295
|
}
|
|
1079
1296
|
}
|
|
1080
1297
|
function writeRelayState(state) {
|
|
1081
|
-
if (!
|
|
1082
|
-
|
|
1298
|
+
if (!fs6.existsSync(RELAY_DATA_DIR)) {
|
|
1299
|
+
fs6.mkdirSync(RELAY_DATA_DIR, { recursive: true });
|
|
1083
1300
|
}
|
|
1084
|
-
|
|
1301
|
+
fs6.writeFileSync(RELAY_STATE_PATH, JSON.stringify(state, null, 2), { mode: 384 });
|
|
1085
1302
|
}
|
|
1086
1303
|
function toBase64Url(buf) {
|
|
1087
1304
|
return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
@@ -1103,11 +1320,11 @@ function loadOrCreateRelayDeviceKeys() {
|
|
|
1103
1320
|
return keys;
|
|
1104
1321
|
}
|
|
1105
1322
|
function ensureDevicePaired(keys, operatorToken) {
|
|
1106
|
-
const stateDir = process.env.OPENCLAW_STATE_DIR ||
|
|
1107
|
-
const pairedPath =
|
|
1323
|
+
const stateDir = process.env.OPENCLAW_STATE_DIR || path6.join(os.homedir(), ".openclaw");
|
|
1324
|
+
const pairedPath = path6.join(stateDir, "devices", "paired.json");
|
|
1108
1325
|
let paired = {};
|
|
1109
1326
|
try {
|
|
1110
|
-
paired = JSON.parse(
|
|
1327
|
+
paired = JSON.parse(fs6.readFileSync(pairedPath, "utf-8"));
|
|
1111
1328
|
} catch {
|
|
1112
1329
|
}
|
|
1113
1330
|
if (paired[keys.deviceId]) return false;
|
|
@@ -1129,9 +1346,9 @@ function ensureDevicePaired(keys, operatorToken) {
|
|
|
1129
1346
|
approvedAtMs: now,
|
|
1130
1347
|
displayName: "squad-relay"
|
|
1131
1348
|
};
|
|
1132
|
-
const dir =
|
|
1133
|
-
if (!
|
|
1134
|
-
|
|
1349
|
+
const dir = path6.dirname(pairedPath);
|
|
1350
|
+
if (!fs6.existsSync(dir)) fs6.mkdirSync(dir, { recursive: true });
|
|
1351
|
+
fs6.writeFileSync(pairedPath, JSON.stringify(paired, null, 2), { mode: 384 });
|
|
1135
1352
|
console.log(`[relay-client] Device pairing entry created in paired.json`);
|
|
1136
1353
|
return true;
|
|
1137
1354
|
}
|
|
@@ -1645,6 +1862,7 @@ function squadAppPlugin(api) {
|
|
|
1645
1862
|
const onFsChange = (evt) => broadcastToUsers("fs.change", evt);
|
|
1646
1863
|
registerEntityTools(api, onFsChange);
|
|
1647
1864
|
registerFilesystemTools(api);
|
|
1865
|
+
registerSqlTools(api);
|
|
1648
1866
|
registerVersionMethods(api);
|
|
1649
1867
|
api.registerGatewayMethod(
|
|
1650
1868
|
"tools.invoke",
|
package/openclaw.plugin.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"fs.allowedRoots": {
|
|
10
10
|
"type": "array",
|
|
11
11
|
"items": { "type": "string" },
|
|
12
|
-
"description": "Restrict fs_read/fs_write/fs_list/fs_delete to these directories. Empty or omitted = allow all."
|
|
12
|
+
"description": "Restrict fs_read/fs_write/fs_list/fs_delete/fs_rename to these directories. Empty or omitted = allow all."
|
|
13
13
|
},
|
|
14
14
|
"relay.enabled": {
|
|
15
15
|
"type": "boolean",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "squad-openclaw",
|
|
3
|
-
"version": "2026.2.
|
|
3
|
+
"version": "2026.2.1905",
|
|
4
4
|
"description": "Entity registry, filesystem tools, and version management plugin for OpenClaw gateway",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -35,12 +35,12 @@
|
|
|
35
35
|
"squad"
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@sinclair/typebox": "
|
|
39
|
-
"chokidar": "
|
|
40
|
-
"ws": "
|
|
38
|
+
"@sinclair/typebox": "0.31.28",
|
|
39
|
+
"chokidar": "4.0.3",
|
|
40
|
+
"ws": "8.18.3"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"tsup": "
|
|
44
|
-
"typescript": "
|
|
43
|
+
"tsup": "8.5.1",
|
|
44
|
+
"typescript": "5.9.3"
|
|
45
45
|
}
|
|
46
46
|
}
|