clawmarketbot 0.1.8 → 0.1.10
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.js +252 -93
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -212,9 +212,9 @@ Hey ${username}! You're now logged in.
|
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
// src/commands/config.ts
|
|
215
|
-
import
|
|
216
|
-
import
|
|
217
|
-
import
|
|
215
|
+
import fs5 from "fs/promises";
|
|
216
|
+
import path4 from "path";
|
|
217
|
+
import os4 from "os";
|
|
218
218
|
import { createHash, randomUUID } from "crypto";
|
|
219
219
|
import { spawn } from "child_process";
|
|
220
220
|
import JSON52 from "json5";
|
|
@@ -587,6 +587,52 @@ async function loadOpenClawContext() {
|
|
|
587
587
|
};
|
|
588
588
|
}
|
|
589
589
|
|
|
590
|
+
// src/lib/snapshots.ts
|
|
591
|
+
import path3 from "path";
|
|
592
|
+
import fs4 from "fs/promises";
|
|
593
|
+
import os3 from "os";
|
|
594
|
+
var SNAPSHOT_EXPIRY_MS = 24 * 60 * 60 * 1e3;
|
|
595
|
+
function getSnapshotsDir() {
|
|
596
|
+
return path3.join(os3.homedir(), ".clawmarket", "snapshots");
|
|
597
|
+
}
|
|
598
|
+
function snapshotFilePath() {
|
|
599
|
+
return path3.join(getSnapshotsDir(), "install-snapshot.json");
|
|
600
|
+
}
|
|
601
|
+
async function saveInstallSnapshot(data) {
|
|
602
|
+
const dir = getSnapshotsDir();
|
|
603
|
+
await fs4.mkdir(dir, { recursive: true, mode: 448 });
|
|
604
|
+
await fs4.chmod(dir, 448).catch(() => void 0);
|
|
605
|
+
const snapshot = {
|
|
606
|
+
version: 1,
|
|
607
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
608
|
+
...data
|
|
609
|
+
};
|
|
610
|
+
const filePath = snapshotFilePath();
|
|
611
|
+
await fs4.writeFile(filePath, JSON.stringify(snapshot, null, 2), { mode: 384 });
|
|
612
|
+
await fs4.chmod(filePath, 384).catch(() => void 0);
|
|
613
|
+
}
|
|
614
|
+
async function loadLatestSnapshot() {
|
|
615
|
+
const filePath = snapshotFilePath();
|
|
616
|
+
if (!await fileExists(filePath)) return null;
|
|
617
|
+
try {
|
|
618
|
+
const raw = await fs4.readFile(filePath, "utf-8");
|
|
619
|
+
const snapshot = JSON.parse(raw);
|
|
620
|
+
if (snapshot.version !== 1 || !snapshot.createdAt) return null;
|
|
621
|
+
const age = Date.now() - new Date(snapshot.createdAt).getTime();
|
|
622
|
+
if (age > SNAPSHOT_EXPIRY_MS) {
|
|
623
|
+
await deleteSnapshot();
|
|
624
|
+
return null;
|
|
625
|
+
}
|
|
626
|
+
return snapshot;
|
|
627
|
+
} catch {
|
|
628
|
+
return null;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
async function deleteSnapshot() {
|
|
632
|
+
const filePath = snapshotFilePath();
|
|
633
|
+
await fs4.rm(filePath, { force: true }).catch(() => void 0);
|
|
634
|
+
}
|
|
635
|
+
|
|
590
636
|
// src/commands/config.ts
|
|
591
637
|
function isValidDownloadToken(token) {
|
|
592
638
|
return isClawMarketDownloadToken(token);
|
|
@@ -597,13 +643,13 @@ function assertValidDownloadToken(token) {
|
|
|
597
643
|
}
|
|
598
644
|
}
|
|
599
645
|
async function resolveSafeDownloadPath(targetPath) {
|
|
600
|
-
const resolved =
|
|
646
|
+
const resolved = path4.resolve(targetPath);
|
|
601
647
|
if (!await fileExists(resolved)) {
|
|
602
648
|
return { outputPath: resolved, renamed: false };
|
|
603
649
|
}
|
|
604
|
-
const parsed =
|
|
650
|
+
const parsed = path4.parse(resolved);
|
|
605
651
|
for (let i = 1; i <= 1e3; i += 1) {
|
|
606
|
-
const candidate =
|
|
652
|
+
const candidate = path4.join(parsed.dir, `${parsed.name}-${i}${parsed.ext}`);
|
|
607
653
|
if (!await fileExists(candidate)) {
|
|
608
654
|
return { outputPath: candidate, renamed: true };
|
|
609
655
|
}
|
|
@@ -631,7 +677,7 @@ function sanitizeSlugPart(value) {
|
|
|
631
677
|
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32);
|
|
632
678
|
}
|
|
633
679
|
function sanitizeFileName(value) {
|
|
634
|
-
const base =
|
|
680
|
+
const base = path4.basename(value || "").replace(/[^A-Za-z0-9._-]/g, "-");
|
|
635
681
|
return base || "download.bin";
|
|
636
682
|
}
|
|
637
683
|
function parseContentDispositionFileName(headerValue) {
|
|
@@ -697,7 +743,8 @@ async function runCommand(command, args, options = {}) {
|
|
|
697
743
|
return new Promise((resolve, reject) => {
|
|
698
744
|
const child = spawn(command, args, {
|
|
699
745
|
cwd: options.cwd,
|
|
700
|
-
stdio: options.stdio === "inherit" ? "inherit" : "pipe"
|
|
746
|
+
stdio: options.stdio === "inherit" ? "inherit" : "pipe",
|
|
747
|
+
env: options.env
|
|
701
748
|
});
|
|
702
749
|
let stdout = "";
|
|
703
750
|
let stderr = "";
|
|
@@ -749,7 +796,7 @@ async function readBotInstallerConfig(configPath) {
|
|
|
749
796
|
if (!await fileExists(configPath)) {
|
|
750
797
|
throw new Error(`Package is missing required config file: ${configPath}`);
|
|
751
798
|
}
|
|
752
|
-
const raw = await
|
|
799
|
+
const raw = await fs5.readFile(configPath, "utf-8");
|
|
753
800
|
try {
|
|
754
801
|
return JSON.parse(raw);
|
|
755
802
|
} catch (error) {
|
|
@@ -768,68 +815,68 @@ function resolveInstallPaths(botConfig, agentId, stateDir, sourceBase) {
|
|
|
768
815
|
const tplWorkspace = botConfig.paths?.targetWorkspace ?? "workspace-{agent_id}";
|
|
769
816
|
const tplAgentState = botConfig.paths?.targetAgentState ?? "agents/{agent_id}";
|
|
770
817
|
const tplMemoryDb = botConfig.paths?.targetMemoryDb ?? null;
|
|
771
|
-
const targetAgentRoot =
|
|
818
|
+
const targetAgentRoot = path4.join(stateDir, applyTemplate(tplAgentState));
|
|
772
819
|
return {
|
|
773
|
-
sourceWorkspace:
|
|
774
|
-
sourceAgentState:
|
|
775
|
-
sourceMemoryDb: srcMemoryDb ?
|
|
776
|
-
sourceCronJobs: srcCronJobs ?
|
|
777
|
-
targetWorkspace:
|
|
820
|
+
sourceWorkspace: path4.join(sourceBase, srcWorkspace),
|
|
821
|
+
sourceAgentState: path4.join(sourceBase, srcAgentState),
|
|
822
|
+
sourceMemoryDb: srcMemoryDb ? path4.join(sourceBase, srcMemoryDb) : null,
|
|
823
|
+
sourceCronJobs: srcCronJobs ? path4.join(sourceBase, srcCronJobs) : null,
|
|
824
|
+
targetWorkspace: path4.join(stateDir, applyTemplate(tplWorkspace)),
|
|
778
825
|
targetAgentRoot,
|
|
779
|
-
targetAgentDir:
|
|
780
|
-
targetMemoryDb: tplMemoryDb ?
|
|
826
|
+
targetAgentDir: path4.join(targetAgentRoot, "agent"),
|
|
827
|
+
targetMemoryDb: tplMemoryDb ? path4.join(stateDir, applyTemplate(tplMemoryDb)) : null
|
|
781
828
|
};
|
|
782
829
|
}
|
|
783
830
|
async function findSourceRoot(extractedDir) {
|
|
784
|
-
const queue = [
|
|
831
|
+
const queue = [path4.resolve(extractedDir)];
|
|
785
832
|
const seen = /* @__PURE__ */ new Set();
|
|
786
833
|
while (queue.length > 0) {
|
|
787
834
|
const current = queue.shift();
|
|
788
835
|
if (!current || seen.has(current)) continue;
|
|
789
836
|
seen.add(current);
|
|
790
|
-
if (await fileExists(
|
|
837
|
+
if (await fileExists(path4.join(current, "source", "bot-config.json"))) {
|
|
791
838
|
return current;
|
|
792
839
|
}
|
|
793
840
|
let entries;
|
|
794
841
|
try {
|
|
795
|
-
entries = await
|
|
842
|
+
entries = await fs5.readdir(current, { withFileTypes: true });
|
|
796
843
|
} catch {
|
|
797
844
|
continue;
|
|
798
845
|
}
|
|
799
846
|
for (const entry of entries) {
|
|
800
847
|
if (!entry.isDirectory() || entry.name === "__MACOSX") continue;
|
|
801
|
-
queue.push(
|
|
848
|
+
queue.push(path4.join(current, entry.name));
|
|
802
849
|
}
|
|
803
850
|
}
|
|
804
851
|
return null;
|
|
805
852
|
}
|
|
806
853
|
async function copyDirRecursive(src, dest) {
|
|
807
|
-
await
|
|
808
|
-
const entries = await
|
|
854
|
+
await fs5.mkdir(dest, { recursive: true });
|
|
855
|
+
const entries = await fs5.readdir(src, { withFileTypes: true });
|
|
809
856
|
for (const entry of entries) {
|
|
810
|
-
const srcPath =
|
|
811
|
-
const destPath =
|
|
857
|
+
const srcPath = path4.join(src, entry.name);
|
|
858
|
+
const destPath = path4.join(dest, entry.name);
|
|
812
859
|
if (entry.isDirectory()) {
|
|
813
860
|
await copyDirRecursive(srcPath, destPath);
|
|
814
861
|
} else {
|
|
815
|
-
await
|
|
862
|
+
await fs5.copyFile(srcPath, destPath);
|
|
816
863
|
}
|
|
817
864
|
}
|
|
818
865
|
}
|
|
819
866
|
async function stripMacOsJunk(dir) {
|
|
820
|
-
const macosDir =
|
|
821
|
-
await
|
|
867
|
+
const macosDir = path4.join(dir, "__MACOSX");
|
|
868
|
+
await fs5.rm(macosDir, { recursive: true, force: true }).catch(() => void 0);
|
|
822
869
|
async function removeDsStore(d) {
|
|
823
870
|
let entries;
|
|
824
871
|
try {
|
|
825
|
-
entries = await
|
|
872
|
+
entries = await fs5.readdir(d, { withFileTypes: true });
|
|
826
873
|
} catch {
|
|
827
874
|
return;
|
|
828
875
|
}
|
|
829
876
|
for (const entry of entries) {
|
|
830
|
-
const full =
|
|
877
|
+
const full = path4.join(d, entry.name);
|
|
831
878
|
if (entry.isFile() && entry.name === ".DS_Store") {
|
|
832
|
-
await
|
|
879
|
+
await fs5.rm(full, { force: true }).catch(() => void 0);
|
|
833
880
|
} else if (entry.isDirectory()) {
|
|
834
881
|
await removeDsStore(full);
|
|
835
882
|
}
|
|
@@ -838,7 +885,7 @@ async function stripMacOsJunk(dir) {
|
|
|
838
885
|
await removeDsStore(dir);
|
|
839
886
|
}
|
|
840
887
|
async function buildOpenClawConfigPatch(configPath, agentId, agentName, botConfig, installPaths, existingAgents = []) {
|
|
841
|
-
const raw = await
|
|
888
|
+
const raw = await fs5.readFile(configPath, "utf-8");
|
|
842
889
|
const config = JSON52.parse(raw);
|
|
843
890
|
const agentEntry = {
|
|
844
891
|
id: agentId,
|
|
@@ -886,7 +933,7 @@ async function buildOpenClawConfigPatch(configPath, agentId, agentName, botConfi
|
|
|
886
933
|
}
|
|
887
934
|
async function prepareCronJobsFromBundle(sourceCronPath, cronStorePath, agentId) {
|
|
888
935
|
if (!sourceCronPath || !await fileExists(sourceCronPath)) return null;
|
|
889
|
-
const raw = await
|
|
936
|
+
const raw = await fs5.readFile(sourceCronPath, "utf-8");
|
|
890
937
|
const bundleJobs = normalizeCronJobs(JSON.parse(raw));
|
|
891
938
|
if (bundleJobs.length === 0) return null;
|
|
892
939
|
const shouldInstall = await resolveCronChoice(true);
|
|
@@ -902,7 +949,7 @@ async function prepareCronJobsFromBundle(sourceCronPath, cronStorePath, agentId)
|
|
|
902
949
|
let existingJobs = [];
|
|
903
950
|
if (await fileExists(cronStorePath)) {
|
|
904
951
|
existed = true;
|
|
905
|
-
backupContent = await
|
|
952
|
+
backupContent = await fs5.readFile(cronStorePath, "utf-8");
|
|
906
953
|
existingJobs = normalizeCronJobs(JSON52.parse(backupContent));
|
|
907
954
|
}
|
|
908
955
|
const existingKeys = new Set(
|
|
@@ -935,7 +982,7 @@ async function setupApiKeysFromConfig(botConfig, configPath, envPath) {
|
|
|
935
982
|
console.log("");
|
|
936
983
|
const key = await askOptional(brave.prompt ?? "Brave Search API key", brave.url);
|
|
937
984
|
if (key) {
|
|
938
|
-
const raw = await
|
|
985
|
+
const raw = await fs5.readFile(configPath, "utf-8");
|
|
939
986
|
const config = JSON52.parse(raw);
|
|
940
987
|
const tools = config.tools ?? {};
|
|
941
988
|
const web = tools.web ?? {};
|
|
@@ -944,7 +991,7 @@ async function setupApiKeysFromConfig(botConfig, configPath, envPath) {
|
|
|
944
991
|
web.search = search;
|
|
945
992
|
tools.web = web;
|
|
946
993
|
config.tools = tools;
|
|
947
|
-
await
|
|
994
|
+
await fs5.writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
948
995
|
console.log(" Brave Search key saved to openclaw.json");
|
|
949
996
|
} else {
|
|
950
997
|
console.log(" Skipped Brave Search key.");
|
|
@@ -952,10 +999,10 @@ async function setupApiKeysFromConfig(botConfig, configPath, envPath) {
|
|
|
952
999
|
}
|
|
953
1000
|
const envKeys = botConfig.apiKeys.env ?? [];
|
|
954
1001
|
if (envKeys.length > 0) {
|
|
955
|
-
await
|
|
1002
|
+
await fs5.mkdir(path4.dirname(envPath), { recursive: true });
|
|
956
1003
|
let envContent = "";
|
|
957
1004
|
try {
|
|
958
|
-
envContent = await
|
|
1005
|
+
envContent = await fs5.readFile(envPath, "utf-8");
|
|
959
1006
|
} catch {
|
|
960
1007
|
}
|
|
961
1008
|
let keysWritten = 0;
|
|
@@ -974,7 +1021,7 @@ ${keyDef.key}=${val}`;
|
|
|
974
1021
|
}
|
|
975
1022
|
}
|
|
976
1023
|
if (keysWritten > 0) {
|
|
977
|
-
await
|
|
1024
|
+
await fs5.writeFile(envPath, envContent.trim() + "\n", "utf-8");
|
|
978
1025
|
console.log(`
|
|
979
1026
|
${keysWritten} key(s) saved to .env`);
|
|
980
1027
|
}
|
|
@@ -986,13 +1033,13 @@ async function validateInstalledWorkspace(workspacePath, botConfig) {
|
|
|
986
1033
|
if (requiredFiles.length === 0 && requiredDirs.length === 0) return;
|
|
987
1034
|
const warnings = [];
|
|
988
1035
|
for (const file of requiredFiles) {
|
|
989
|
-
if (!await fileExists(
|
|
1036
|
+
if (!await fileExists(path4.join(workspacePath, file))) {
|
|
990
1037
|
warnings.push(`Missing expected file: ${file}`);
|
|
991
1038
|
}
|
|
992
1039
|
}
|
|
993
1040
|
for (const dir of requiredDirs) {
|
|
994
1041
|
try {
|
|
995
|
-
const stat = await
|
|
1042
|
+
const stat = await fs5.stat(path4.join(workspacePath, dir));
|
|
996
1043
|
if (!stat.isDirectory()) warnings.push(`Expected directory but found file: ${dir}`);
|
|
997
1044
|
} catch {
|
|
998
1045
|
warnings.push(`Missing expected directory: ${dir}`);
|
|
@@ -1027,46 +1074,46 @@ async function commitInstallTransaction(input2) {
|
|
|
1027
1074
|
let movedAgentDir = false;
|
|
1028
1075
|
let copiedMemoryDb = false;
|
|
1029
1076
|
try {
|
|
1030
|
-
await
|
|
1031
|
-
await
|
|
1077
|
+
await fs5.mkdir(path4.dirname(input2.workspacePath), { recursive: true });
|
|
1078
|
+
await fs5.rename(input2.stagedWorkspacePath, input2.workspacePath);
|
|
1032
1079
|
movedWorkspace = true;
|
|
1033
|
-
await
|
|
1034
|
-
await
|
|
1080
|
+
await fs5.mkdir(path4.dirname(input2.agentDirPath), { recursive: true });
|
|
1081
|
+
await fs5.rename(input2.stagedAgentDirPath, input2.agentDirPath);
|
|
1035
1082
|
movedAgentDir = true;
|
|
1036
1083
|
if (input2.memoryDbStagedPath && input2.memoryDbTargetPath && await fileExists(input2.memoryDbStagedPath)) {
|
|
1037
|
-
await
|
|
1038
|
-
await
|
|
1084
|
+
await fs5.mkdir(path4.dirname(input2.memoryDbTargetPath), { recursive: true });
|
|
1085
|
+
await fs5.copyFile(input2.memoryDbStagedPath, input2.memoryDbTargetPath);
|
|
1039
1086
|
copiedMemoryDb = true;
|
|
1040
1087
|
}
|
|
1041
1088
|
if (input2.cronContent !== null) {
|
|
1042
|
-
await
|
|
1043
|
-
await
|
|
1089
|
+
await fs5.mkdir(path4.dirname(input2.cronStorePath), { recursive: true });
|
|
1090
|
+
await fs5.writeFile(input2.cronStorePath, input2.cronContent, "utf-8");
|
|
1044
1091
|
}
|
|
1045
|
-
await
|
|
1092
|
+
await fs5.writeFile(input2.configPath, input2.configContent, "utf-8");
|
|
1046
1093
|
} catch (error) {
|
|
1047
1094
|
if (input2.cronContent !== null) {
|
|
1048
1095
|
if (input2.cronExisted && input2.cronBackupContent !== null) {
|
|
1049
|
-
await
|
|
1096
|
+
await fs5.writeFile(input2.cronStorePath, input2.cronBackupContent, "utf-8").catch(() => void 0);
|
|
1050
1097
|
} else {
|
|
1051
|
-
await
|
|
1098
|
+
await fs5.rm(input2.cronStorePath, { force: true }).catch(() => void 0);
|
|
1052
1099
|
}
|
|
1053
1100
|
}
|
|
1054
1101
|
if (copiedMemoryDb && input2.memoryDbTargetPath) {
|
|
1055
|
-
await
|
|
1102
|
+
await fs5.rm(input2.memoryDbTargetPath, { force: true }).catch(() => void 0);
|
|
1056
1103
|
}
|
|
1057
|
-
await
|
|
1104
|
+
await fs5.writeFile(input2.configPath, input2.configBackupContent, "utf-8").catch(() => void 0);
|
|
1058
1105
|
if (movedWorkspace) {
|
|
1059
|
-
await
|
|
1106
|
+
await fs5.rm(input2.workspacePath, { recursive: true, force: true }).catch(() => void 0);
|
|
1060
1107
|
}
|
|
1061
1108
|
if (movedAgentDir) {
|
|
1062
|
-
await
|
|
1109
|
+
await fs5.rm(input2.agentRootPath, { recursive: true, force: true }).catch(() => void 0);
|
|
1063
1110
|
}
|
|
1064
1111
|
const message = error instanceof Error ? error.message : "unknown failure";
|
|
1065
1112
|
throw new Error(`Install failed and was rolled back: ${message}`);
|
|
1066
1113
|
}
|
|
1067
1114
|
}
|
|
1068
1115
|
async function buildOpenClawConfigUnpatch(configPath, agentId) {
|
|
1069
|
-
const raw = await
|
|
1116
|
+
const raw = await fs5.readFile(configPath, "utf-8");
|
|
1070
1117
|
const config = JSON52.parse(raw);
|
|
1071
1118
|
const agents = config.agents ?? {};
|
|
1072
1119
|
if (!Array.isArray(agents.list)) {
|
|
@@ -1089,7 +1136,7 @@ async function buildOpenClawConfigUnpatch(configPath, agentId) {
|
|
|
1089
1136
|
}
|
|
1090
1137
|
async function buildCronJobsUnpatch(cronStorePath, agentId) {
|
|
1091
1138
|
if (!await fileExists(cronStorePath)) return null;
|
|
1092
|
-
const raw = await
|
|
1139
|
+
const raw = await fs5.readFile(cronStorePath, "utf-8");
|
|
1093
1140
|
let parsed;
|
|
1094
1141
|
try {
|
|
1095
1142
|
parsed = JSON52.parse(raw);
|
|
@@ -1135,7 +1182,7 @@ function buildUninstallPlan(context, agentId) {
|
|
|
1135
1182
|
path: agent.workspace,
|
|
1136
1183
|
description: `Workspace directory`
|
|
1137
1184
|
});
|
|
1138
|
-
const agentRootDir =
|
|
1185
|
+
const agentRootDir = path4.dirname(agent.agentDir);
|
|
1139
1186
|
removals.push({
|
|
1140
1187
|
type: "directory",
|
|
1141
1188
|
path: agentRootDir,
|
|
@@ -1160,37 +1207,37 @@ function buildUninstallPlan(context, agentId) {
|
|
|
1160
1207
|
}
|
|
1161
1208
|
async function commitUninstallTransaction(plan) {
|
|
1162
1209
|
try {
|
|
1163
|
-
await
|
|
1210
|
+
await fs5.writeFile(plan.configPath, plan.configPatch.after, "utf-8");
|
|
1164
1211
|
} catch (error) {
|
|
1165
1212
|
const msg = error instanceof Error ? error.message : "unknown failure";
|
|
1166
1213
|
throw new Error(`Uninstall failed while patching openclaw.json: ${msg}`);
|
|
1167
1214
|
}
|
|
1168
1215
|
if (plan.cronPatch) {
|
|
1169
1216
|
try {
|
|
1170
|
-
await
|
|
1217
|
+
await fs5.writeFile(plan.cronStorePath, plan.cronPatch.after, "utf-8");
|
|
1171
1218
|
} catch (error) {
|
|
1172
|
-
await
|
|
1219
|
+
await fs5.writeFile(plan.configPath, plan.configPatch.before, "utf-8").catch(() => void 0);
|
|
1173
1220
|
const msg = error instanceof Error ? error.message : "unknown failure";
|
|
1174
1221
|
throw new Error(`Uninstall failed while patching cron jobs (rolled back openclaw.json): ${msg}`);
|
|
1175
1222
|
}
|
|
1176
1223
|
}
|
|
1177
1224
|
try {
|
|
1178
|
-
await
|
|
1225
|
+
await fs5.rm(plan.agent.workspace, { recursive: true, force: true });
|
|
1179
1226
|
} catch (error) {
|
|
1180
|
-
await
|
|
1227
|
+
await fs5.writeFile(plan.configPath, plan.configPatch.before, "utf-8").catch(() => void 0);
|
|
1181
1228
|
if (plan.cronPatch) {
|
|
1182
|
-
await
|
|
1229
|
+
await fs5.writeFile(plan.cronStorePath, plan.cronPatch.before, "utf-8").catch(() => void 0);
|
|
1183
1230
|
}
|
|
1184
1231
|
const msg = error instanceof Error ? error.message : "unknown failure";
|
|
1185
1232
|
throw new Error(`Uninstall failed while removing workspace (rolled back config files): ${msg}`);
|
|
1186
1233
|
}
|
|
1187
|
-
const agentRootDir =
|
|
1234
|
+
const agentRootDir = path4.dirname(plan.agent.agentDir);
|
|
1188
1235
|
try {
|
|
1189
|
-
await
|
|
1236
|
+
await fs5.rm(agentRootDir, { recursive: true, force: true });
|
|
1190
1237
|
} catch (error) {
|
|
1191
|
-
await
|
|
1238
|
+
await fs5.writeFile(plan.configPath, plan.configPatch.before, "utf-8").catch(() => void 0);
|
|
1192
1239
|
if (plan.cronPatch) {
|
|
1193
|
-
await
|
|
1240
|
+
await fs5.writeFile(plan.cronStorePath, plan.cronPatch.before, "utf-8").catch(() => void 0);
|
|
1194
1241
|
}
|
|
1195
1242
|
const msg = error instanceof Error ? error.message : "unknown failure";
|
|
1196
1243
|
throw new Error(`Uninstall failed while removing agent directory (rolled back config files): ${msg}`);
|
|
@@ -1220,12 +1267,12 @@ function registerConfigCommands(program2) {
|
|
|
1220
1267
|
}
|
|
1221
1268
|
const fileFromHeader = parseContentDispositionFileName(response.headers.get("content-disposition"));
|
|
1222
1269
|
const fallbackFile = `clawmarket-${token}.bin`;
|
|
1223
|
-
const preferredPath =
|
|
1270
|
+
const preferredPath = path4.resolve(fileFromHeader || fallbackFile);
|
|
1224
1271
|
const { outputPath, renamed } = await resolveSafeDownloadPath(preferredPath);
|
|
1225
1272
|
const bytes = Buffer.from(await response.arrayBuffer());
|
|
1226
1273
|
verifyDownloadedArtifactChecksum(bytes, response.headers.get(CLAWMARKET_ARTIFACT_SHA256_HEADER));
|
|
1227
|
-
await
|
|
1228
|
-
await
|
|
1274
|
+
await fs5.mkdir(path4.dirname(outputPath), { recursive: true });
|
|
1275
|
+
await fs5.writeFile(outputPath, bytes);
|
|
1229
1276
|
if (renamed) {
|
|
1230
1277
|
console.log(`Existing file detected, saved as: ${outputPath}`);
|
|
1231
1278
|
}
|
|
@@ -1242,14 +1289,14 @@ function registerConfigCommands(program2) {
|
|
|
1242
1289
|
const server = getDefaultServer();
|
|
1243
1290
|
const context = await loadOpenClawContext();
|
|
1244
1291
|
const artifact = await fetchDownloadArtifact(server, token);
|
|
1245
|
-
const tempRoot = await
|
|
1246
|
-
|
|
1292
|
+
const tempRoot = await fs5.mkdtemp(
|
|
1293
|
+
path4.join(os4.tmpdir(), `.clawmarket-install-${randomUUID().slice(0, 8)}-`)
|
|
1247
1294
|
);
|
|
1248
1295
|
try {
|
|
1249
|
-
const artifactPath =
|
|
1250
|
-
const extractedPath =
|
|
1251
|
-
await
|
|
1252
|
-
await
|
|
1296
|
+
const artifactPath = path4.join(tempRoot, sanitizeFileName(artifact.fileName));
|
|
1297
|
+
const extractedPath = path4.join(tempRoot, "extracted");
|
|
1298
|
+
await fs5.mkdir(extractedPath, { recursive: true });
|
|
1299
|
+
await fs5.writeFile(artifactPath, artifact.bytes);
|
|
1253
1300
|
await extractZipArtifact(artifactPath, extractedPath);
|
|
1254
1301
|
await stripMacOsJunk(extractedPath);
|
|
1255
1302
|
const sourceRoot = await findSourceRoot(extractedPath);
|
|
@@ -1258,8 +1305,8 @@ function registerConfigCommands(program2) {
|
|
|
1258
1305
|
"Downloaded artifact is not an installable bot package (missing source/bot-config.json)."
|
|
1259
1306
|
);
|
|
1260
1307
|
}
|
|
1261
|
-
const sourceBase =
|
|
1262
|
-
const botConfig = await readBotInstallerConfig(
|
|
1308
|
+
const sourceBase = path4.join(sourceRoot, "source");
|
|
1309
|
+
const botConfig = await readBotInstallerConfig(path4.join(sourceBase, "bot-config.json"));
|
|
1263
1310
|
if (typeof botConfig.installerSpecVersion === "number" && botConfig.installerSpecVersion !== 1) {
|
|
1264
1311
|
console.warn(
|
|
1265
1312
|
`Warning: installerSpecVersion=${botConfig.installerSpecVersion} (this CLI currently expects version 1).`
|
|
@@ -1274,8 +1321,8 @@ function registerConfigCommands(program2) {
|
|
|
1274
1321
|
throw new Error(`Agent id "${agentId}" already exists. Choose another id.`);
|
|
1275
1322
|
}
|
|
1276
1323
|
const installPaths = resolveInstallPaths(botConfig, agentId, context.paths.stateDir, sourceBase);
|
|
1277
|
-
const stagedWorkspacePath =
|
|
1278
|
-
const stagedAgentDirPath =
|
|
1324
|
+
const stagedWorkspacePath = path4.join(tempRoot, "staged-workspace");
|
|
1325
|
+
const stagedAgentDirPath = path4.join(tempRoot, "staged-agent");
|
|
1279
1326
|
await copyDirRecursive(installPaths.sourceWorkspace, stagedWorkspacePath);
|
|
1280
1327
|
await copyDirRecursive(installPaths.sourceAgentState, stagedAgentDirPath);
|
|
1281
1328
|
const { configContent, configBackupContent } = await buildOpenClawConfigPatch(
|
|
@@ -1291,6 +1338,20 @@ function registerConfigCommands(program2) {
|
|
|
1291
1338
|
context.paths.cronStorePath,
|
|
1292
1339
|
agentId
|
|
1293
1340
|
);
|
|
1341
|
+
await saveInstallSnapshot({
|
|
1342
|
+
agentId,
|
|
1343
|
+
agentName,
|
|
1344
|
+
openclawConfigBefore: configBackupContent,
|
|
1345
|
+
cronStoreBefore: cronResult?.backupContent ?? null,
|
|
1346
|
+
cronStoreExisted: cronResult?.existed ?? false,
|
|
1347
|
+
installedPaths: {
|
|
1348
|
+
workspace: installPaths.targetWorkspace,
|
|
1349
|
+
agentRoot: installPaths.targetAgentRoot,
|
|
1350
|
+
memoryDb: installPaths.targetMemoryDb ?? null
|
|
1351
|
+
},
|
|
1352
|
+
configPath: context.paths.configPath,
|
|
1353
|
+
cronStorePath: context.paths.cronStorePath
|
|
1354
|
+
});
|
|
1294
1355
|
console.log("\nInstalling...");
|
|
1295
1356
|
await commitInstallTransaction({
|
|
1296
1357
|
stagedWorkspacePath,
|
|
@@ -1309,15 +1370,29 @@ function registerConfigCommands(program2) {
|
|
|
1309
1370
|
memoryDbTargetPath: installPaths.targetMemoryDb
|
|
1310
1371
|
});
|
|
1311
1372
|
await setupApiKeysFromConfig(botConfig, context.paths.configPath, context.paths.envPath);
|
|
1312
|
-
const installerShPath =
|
|
1373
|
+
const installerShPath = path4.join(sourceBase, "installer.sh");
|
|
1313
1374
|
if (await fileExists(installerShPath)) {
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1375
|
+
if (botConfig.agent?.id) {
|
|
1376
|
+
console.log("\nSkipping installer.sh \u2014 bot-config.json agent declaration handled by CLI.");
|
|
1377
|
+
} else {
|
|
1378
|
+
console.log("\nRunning post-install hook (installer.sh)...");
|
|
1379
|
+
const hookPath = path4.join(sourceRoot, "_clawmarket_installer.sh");
|
|
1380
|
+
await fs5.copyFile(installerShPath, hookPath);
|
|
1381
|
+
await runCommand("bash", [hookPath, context.paths.stateDir], {
|
|
1382
|
+
cwd: sourceRoot,
|
|
1383
|
+
stdio: "inherit",
|
|
1384
|
+
env: {
|
|
1385
|
+
...process.env,
|
|
1386
|
+
CLAWMARKET_CLI_INSTALL: "1",
|
|
1387
|
+
CLAWMARKET_AGENT_ID: agentId,
|
|
1388
|
+
CLAWMARKET_AGENT_NAME: agentName,
|
|
1389
|
+
CLAWMARKET_WORKSPACE: installPaths.targetWorkspace,
|
|
1390
|
+
CLAWMARKET_AGENT_ROOT: installPaths.targetAgentRoot,
|
|
1391
|
+
CLAWMARKET_CONFIG_PATH: context.paths.configPath,
|
|
1392
|
+
CLAWMARKET_STATE_DIR: context.paths.stateDir
|
|
1393
|
+
}
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1321
1396
|
}
|
|
1322
1397
|
await validateInstalledWorkspace(installPaths.targetWorkspace, botConfig);
|
|
1323
1398
|
const refreshed = await loadOpenClawContext();
|
|
@@ -1342,7 +1417,7 @@ function registerConfigCommands(program2) {
|
|
|
1342
1417
|
console.log(" 1) openclaw agents list");
|
|
1343
1418
|
console.log(" 2) openclaw gateway restart");
|
|
1344
1419
|
} finally {
|
|
1345
|
-
await
|
|
1420
|
+
await fs5.rm(tempRoot, { recursive: true, force: true }).catch(() => void 0);
|
|
1346
1421
|
}
|
|
1347
1422
|
} catch (error) {
|
|
1348
1423
|
const message = error instanceof Error ? error.message : "Install failed";
|
|
@@ -1379,7 +1454,7 @@ function registerConfigCommands(program2) {
|
|
|
1379
1454
|
console.log(` Uninstalled "${agent.name}" (${agent.id})`);
|
|
1380
1455
|
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1381
1456
|
console.log(` Removed workspace: ${agent.workspace}`);
|
|
1382
|
-
console.log(` Removed agent dir: ${
|
|
1457
|
+
console.log(` Removed agent dir: ${path4.dirname(agent.agentDir)}`);
|
|
1383
1458
|
console.log(` Removed from openclaw.json agents.list`);
|
|
1384
1459
|
if (cronPatch) {
|
|
1385
1460
|
console.log(` Removed ${cronPatch.removedCount} cron job(s)`);
|
|
@@ -1391,11 +1466,95 @@ function registerConfigCommands(program2) {
|
|
|
1391
1466
|
process.exit(1);
|
|
1392
1467
|
}
|
|
1393
1468
|
});
|
|
1469
|
+
configCommand.command("restore").description("Restore OpenClaw state to before the last install (snapshots expire after 24 hours)").option("--force", "Skip confirmation prompt").action(async (opts) => {
|
|
1470
|
+
try {
|
|
1471
|
+
const snapshot = await loadLatestSnapshot();
|
|
1472
|
+
if (!snapshot) {
|
|
1473
|
+
console.log("No recent install snapshot found (snapshots expire after 24 hours).");
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
console.log("");
|
|
1477
|
+
console.log(`Restore to state before installing "${snapshot.agentName}" (${snapshot.agentId})?`);
|
|
1478
|
+
console.log("");
|
|
1479
|
+
console.log(` - Restore openclaw.json`);
|
|
1480
|
+
console.log(` - Remove workspace: ${snapshot.installedPaths.workspace}`);
|
|
1481
|
+
console.log(` - Remove agent directory: ${snapshot.installedPaths.agentRoot}`);
|
|
1482
|
+
if (snapshot.installedPaths.memoryDb) {
|
|
1483
|
+
console.log(` - Remove memory DB: ${snapshot.installedPaths.memoryDb}`);
|
|
1484
|
+
}
|
|
1485
|
+
if (snapshot.cronStoreBefore !== null) {
|
|
1486
|
+
console.log(` - Restore cron jobs`);
|
|
1487
|
+
} else if (!snapshot.cronStoreExisted) {
|
|
1488
|
+
}
|
|
1489
|
+
console.log("");
|
|
1490
|
+
if (!opts.force) {
|
|
1491
|
+
const canPrompt = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
1492
|
+
if (!canPrompt) {
|
|
1493
|
+
throw new Error("Restore requires an interactive terminal for confirmation. Use --force to skip.");
|
|
1494
|
+
}
|
|
1495
|
+
const confirmed = await confirm("Proceed with restore?", false);
|
|
1496
|
+
if (!confirmed) {
|
|
1497
|
+
console.log("Restore cancelled.");
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
await fs5.writeFile(snapshot.configPath, snapshot.openclawConfigBefore, "utf-8");
|
|
1502
|
+
if (snapshot.cronStoreBefore !== null) {
|
|
1503
|
+
await fs5.writeFile(snapshot.cronStorePath, snapshot.cronStoreBefore, "utf-8");
|
|
1504
|
+
} else if (!snapshot.cronStoreExisted) {
|
|
1505
|
+
await fs5.rm(snapshot.cronStorePath, { force: true }).catch(() => void 0);
|
|
1506
|
+
}
|
|
1507
|
+
await fs5.rm(snapshot.installedPaths.workspace, { recursive: true, force: true }).catch(() => void 0);
|
|
1508
|
+
await fs5.rm(snapshot.installedPaths.agentRoot, { recursive: true, force: true }).catch(() => void 0);
|
|
1509
|
+
if (snapshot.installedPaths.memoryDb) {
|
|
1510
|
+
await fs5.rm(snapshot.installedPaths.memoryDb, { force: true }).catch(() => void 0);
|
|
1511
|
+
}
|
|
1512
|
+
await deleteSnapshot();
|
|
1513
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1514
|
+
console.log(` Restored to pre-install state`);
|
|
1515
|
+
console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1516
|
+
console.log(` Restored openclaw.json`);
|
|
1517
|
+
console.log(` Removed workspace: ${snapshot.installedPaths.workspace}`);
|
|
1518
|
+
console.log(` Removed agent dir: ${snapshot.installedPaths.agentRoot}`);
|
|
1519
|
+
if (snapshot.installedPaths.memoryDb) {
|
|
1520
|
+
console.log(` Removed memory DB: ${snapshot.installedPaths.memoryDb}`);
|
|
1521
|
+
}
|
|
1522
|
+
if (snapshot.cronStoreBefore !== null) {
|
|
1523
|
+
console.log(` Restored cron jobs`);
|
|
1524
|
+
}
|
|
1525
|
+
console.log("");
|
|
1526
|
+
} catch (error) {
|
|
1527
|
+
const message = error instanceof Error ? error.message : "Restore failed";
|
|
1528
|
+
console.error(message);
|
|
1529
|
+
process.exit(1);
|
|
1530
|
+
}
|
|
1531
|
+
});
|
|
1532
|
+
configCommand.command("snapshots").description("List available install snapshots").action(async () => {
|
|
1533
|
+
try {
|
|
1534
|
+
const snapshot = await loadLatestSnapshot();
|
|
1535
|
+
if (!snapshot) {
|
|
1536
|
+
console.log("No install snapshots available.");
|
|
1537
|
+
return;
|
|
1538
|
+
}
|
|
1539
|
+
const created = new Date(snapshot.createdAt);
|
|
1540
|
+
const expires = new Date(created.getTime() + 24 * 60 * 60 * 1e3);
|
|
1541
|
+
const formatDate = (d) => d.toISOString().replace("T", " ").replace(/\.\d{3}Z$/, "");
|
|
1542
|
+
console.log("");
|
|
1543
|
+
console.log(` Agent: ${snapshot.agentId} (${snapshot.agentName})`);
|
|
1544
|
+
console.log(` Created: ${formatDate(created)}`);
|
|
1545
|
+
console.log(` Expires: ${formatDate(expires)}`);
|
|
1546
|
+
console.log("");
|
|
1547
|
+
} catch (error) {
|
|
1548
|
+
const message = error instanceof Error ? error.message : "Failed to list snapshots";
|
|
1549
|
+
console.error(message);
|
|
1550
|
+
process.exit(1);
|
|
1551
|
+
}
|
|
1552
|
+
});
|
|
1394
1553
|
}
|
|
1395
1554
|
|
|
1396
1555
|
// src/index.ts
|
|
1397
1556
|
var program = new Command();
|
|
1398
|
-
program.name("clawmarketbot").description("CLI tool for ClawMarket - discover, download, and install OpenClaw configs").version("0.1.
|
|
1557
|
+
program.name("clawmarketbot").description("CLI tool for ClawMarket - discover, download, and install OpenClaw configs").version("0.1.10");
|
|
1399
1558
|
registerAuthCommands(program);
|
|
1400
1559
|
registerConfigCommands(program);
|
|
1401
1560
|
program.parse();
|