clawmarketbot 0.1.8 → 0.1.9
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 +231 -87
- 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) {
|
|
@@ -749,7 +795,7 @@ async function readBotInstallerConfig(configPath) {
|
|
|
749
795
|
if (!await fileExists(configPath)) {
|
|
750
796
|
throw new Error(`Package is missing required config file: ${configPath}`);
|
|
751
797
|
}
|
|
752
|
-
const raw = await
|
|
798
|
+
const raw = await fs5.readFile(configPath, "utf-8");
|
|
753
799
|
try {
|
|
754
800
|
return JSON.parse(raw);
|
|
755
801
|
} catch (error) {
|
|
@@ -768,68 +814,68 @@ function resolveInstallPaths(botConfig, agentId, stateDir, sourceBase) {
|
|
|
768
814
|
const tplWorkspace = botConfig.paths?.targetWorkspace ?? "workspace-{agent_id}";
|
|
769
815
|
const tplAgentState = botConfig.paths?.targetAgentState ?? "agents/{agent_id}";
|
|
770
816
|
const tplMemoryDb = botConfig.paths?.targetMemoryDb ?? null;
|
|
771
|
-
const targetAgentRoot =
|
|
817
|
+
const targetAgentRoot = path4.join(stateDir, applyTemplate(tplAgentState));
|
|
772
818
|
return {
|
|
773
|
-
sourceWorkspace:
|
|
774
|
-
sourceAgentState:
|
|
775
|
-
sourceMemoryDb: srcMemoryDb ?
|
|
776
|
-
sourceCronJobs: srcCronJobs ?
|
|
777
|
-
targetWorkspace:
|
|
819
|
+
sourceWorkspace: path4.join(sourceBase, srcWorkspace),
|
|
820
|
+
sourceAgentState: path4.join(sourceBase, srcAgentState),
|
|
821
|
+
sourceMemoryDb: srcMemoryDb ? path4.join(sourceBase, srcMemoryDb) : null,
|
|
822
|
+
sourceCronJobs: srcCronJobs ? path4.join(sourceBase, srcCronJobs) : null,
|
|
823
|
+
targetWorkspace: path4.join(stateDir, applyTemplate(tplWorkspace)),
|
|
778
824
|
targetAgentRoot,
|
|
779
|
-
targetAgentDir:
|
|
780
|
-
targetMemoryDb: tplMemoryDb ?
|
|
825
|
+
targetAgentDir: path4.join(targetAgentRoot, "agent"),
|
|
826
|
+
targetMemoryDb: tplMemoryDb ? path4.join(stateDir, applyTemplate(tplMemoryDb)) : null
|
|
781
827
|
};
|
|
782
828
|
}
|
|
783
829
|
async function findSourceRoot(extractedDir) {
|
|
784
|
-
const queue = [
|
|
830
|
+
const queue = [path4.resolve(extractedDir)];
|
|
785
831
|
const seen = /* @__PURE__ */ new Set();
|
|
786
832
|
while (queue.length > 0) {
|
|
787
833
|
const current = queue.shift();
|
|
788
834
|
if (!current || seen.has(current)) continue;
|
|
789
835
|
seen.add(current);
|
|
790
|
-
if (await fileExists(
|
|
836
|
+
if (await fileExists(path4.join(current, "source", "bot-config.json"))) {
|
|
791
837
|
return current;
|
|
792
838
|
}
|
|
793
839
|
let entries;
|
|
794
840
|
try {
|
|
795
|
-
entries = await
|
|
841
|
+
entries = await fs5.readdir(current, { withFileTypes: true });
|
|
796
842
|
} catch {
|
|
797
843
|
continue;
|
|
798
844
|
}
|
|
799
845
|
for (const entry of entries) {
|
|
800
846
|
if (!entry.isDirectory() || entry.name === "__MACOSX") continue;
|
|
801
|
-
queue.push(
|
|
847
|
+
queue.push(path4.join(current, entry.name));
|
|
802
848
|
}
|
|
803
849
|
}
|
|
804
850
|
return null;
|
|
805
851
|
}
|
|
806
852
|
async function copyDirRecursive(src, dest) {
|
|
807
|
-
await
|
|
808
|
-
const entries = await
|
|
853
|
+
await fs5.mkdir(dest, { recursive: true });
|
|
854
|
+
const entries = await fs5.readdir(src, { withFileTypes: true });
|
|
809
855
|
for (const entry of entries) {
|
|
810
|
-
const srcPath =
|
|
811
|
-
const destPath =
|
|
856
|
+
const srcPath = path4.join(src, entry.name);
|
|
857
|
+
const destPath = path4.join(dest, entry.name);
|
|
812
858
|
if (entry.isDirectory()) {
|
|
813
859
|
await copyDirRecursive(srcPath, destPath);
|
|
814
860
|
} else {
|
|
815
|
-
await
|
|
861
|
+
await fs5.copyFile(srcPath, destPath);
|
|
816
862
|
}
|
|
817
863
|
}
|
|
818
864
|
}
|
|
819
865
|
async function stripMacOsJunk(dir) {
|
|
820
|
-
const macosDir =
|
|
821
|
-
await
|
|
866
|
+
const macosDir = path4.join(dir, "__MACOSX");
|
|
867
|
+
await fs5.rm(macosDir, { recursive: true, force: true }).catch(() => void 0);
|
|
822
868
|
async function removeDsStore(d) {
|
|
823
869
|
let entries;
|
|
824
870
|
try {
|
|
825
|
-
entries = await
|
|
871
|
+
entries = await fs5.readdir(d, { withFileTypes: true });
|
|
826
872
|
} catch {
|
|
827
873
|
return;
|
|
828
874
|
}
|
|
829
875
|
for (const entry of entries) {
|
|
830
|
-
const full =
|
|
876
|
+
const full = path4.join(d, entry.name);
|
|
831
877
|
if (entry.isFile() && entry.name === ".DS_Store") {
|
|
832
|
-
await
|
|
878
|
+
await fs5.rm(full, { force: true }).catch(() => void 0);
|
|
833
879
|
} else if (entry.isDirectory()) {
|
|
834
880
|
await removeDsStore(full);
|
|
835
881
|
}
|
|
@@ -838,7 +884,7 @@ async function stripMacOsJunk(dir) {
|
|
|
838
884
|
await removeDsStore(dir);
|
|
839
885
|
}
|
|
840
886
|
async function buildOpenClawConfigPatch(configPath, agentId, agentName, botConfig, installPaths, existingAgents = []) {
|
|
841
|
-
const raw = await
|
|
887
|
+
const raw = await fs5.readFile(configPath, "utf-8");
|
|
842
888
|
const config = JSON52.parse(raw);
|
|
843
889
|
const agentEntry = {
|
|
844
890
|
id: agentId,
|
|
@@ -886,7 +932,7 @@ async function buildOpenClawConfigPatch(configPath, agentId, agentName, botConfi
|
|
|
886
932
|
}
|
|
887
933
|
async function prepareCronJobsFromBundle(sourceCronPath, cronStorePath, agentId) {
|
|
888
934
|
if (!sourceCronPath || !await fileExists(sourceCronPath)) return null;
|
|
889
|
-
const raw = await
|
|
935
|
+
const raw = await fs5.readFile(sourceCronPath, "utf-8");
|
|
890
936
|
const bundleJobs = normalizeCronJobs(JSON.parse(raw));
|
|
891
937
|
if (bundleJobs.length === 0) return null;
|
|
892
938
|
const shouldInstall = await resolveCronChoice(true);
|
|
@@ -902,7 +948,7 @@ async function prepareCronJobsFromBundle(sourceCronPath, cronStorePath, agentId)
|
|
|
902
948
|
let existingJobs = [];
|
|
903
949
|
if (await fileExists(cronStorePath)) {
|
|
904
950
|
existed = true;
|
|
905
|
-
backupContent = await
|
|
951
|
+
backupContent = await fs5.readFile(cronStorePath, "utf-8");
|
|
906
952
|
existingJobs = normalizeCronJobs(JSON52.parse(backupContent));
|
|
907
953
|
}
|
|
908
954
|
const existingKeys = new Set(
|
|
@@ -935,7 +981,7 @@ async function setupApiKeysFromConfig(botConfig, configPath, envPath) {
|
|
|
935
981
|
console.log("");
|
|
936
982
|
const key = await askOptional(brave.prompt ?? "Brave Search API key", brave.url);
|
|
937
983
|
if (key) {
|
|
938
|
-
const raw = await
|
|
984
|
+
const raw = await fs5.readFile(configPath, "utf-8");
|
|
939
985
|
const config = JSON52.parse(raw);
|
|
940
986
|
const tools = config.tools ?? {};
|
|
941
987
|
const web = tools.web ?? {};
|
|
@@ -944,7 +990,7 @@ async function setupApiKeysFromConfig(botConfig, configPath, envPath) {
|
|
|
944
990
|
web.search = search;
|
|
945
991
|
tools.web = web;
|
|
946
992
|
config.tools = tools;
|
|
947
|
-
await
|
|
993
|
+
await fs5.writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
948
994
|
console.log(" Brave Search key saved to openclaw.json");
|
|
949
995
|
} else {
|
|
950
996
|
console.log(" Skipped Brave Search key.");
|
|
@@ -952,10 +998,10 @@ async function setupApiKeysFromConfig(botConfig, configPath, envPath) {
|
|
|
952
998
|
}
|
|
953
999
|
const envKeys = botConfig.apiKeys.env ?? [];
|
|
954
1000
|
if (envKeys.length > 0) {
|
|
955
|
-
await
|
|
1001
|
+
await fs5.mkdir(path4.dirname(envPath), { recursive: true });
|
|
956
1002
|
let envContent = "";
|
|
957
1003
|
try {
|
|
958
|
-
envContent = await
|
|
1004
|
+
envContent = await fs5.readFile(envPath, "utf-8");
|
|
959
1005
|
} catch {
|
|
960
1006
|
}
|
|
961
1007
|
let keysWritten = 0;
|
|
@@ -974,7 +1020,7 @@ ${keyDef.key}=${val}`;
|
|
|
974
1020
|
}
|
|
975
1021
|
}
|
|
976
1022
|
if (keysWritten > 0) {
|
|
977
|
-
await
|
|
1023
|
+
await fs5.writeFile(envPath, envContent.trim() + "\n", "utf-8");
|
|
978
1024
|
console.log(`
|
|
979
1025
|
${keysWritten} key(s) saved to .env`);
|
|
980
1026
|
}
|
|
@@ -986,13 +1032,13 @@ async function validateInstalledWorkspace(workspacePath, botConfig) {
|
|
|
986
1032
|
if (requiredFiles.length === 0 && requiredDirs.length === 0) return;
|
|
987
1033
|
const warnings = [];
|
|
988
1034
|
for (const file of requiredFiles) {
|
|
989
|
-
if (!await fileExists(
|
|
1035
|
+
if (!await fileExists(path4.join(workspacePath, file))) {
|
|
990
1036
|
warnings.push(`Missing expected file: ${file}`);
|
|
991
1037
|
}
|
|
992
1038
|
}
|
|
993
1039
|
for (const dir of requiredDirs) {
|
|
994
1040
|
try {
|
|
995
|
-
const stat = await
|
|
1041
|
+
const stat = await fs5.stat(path4.join(workspacePath, dir));
|
|
996
1042
|
if (!stat.isDirectory()) warnings.push(`Expected directory but found file: ${dir}`);
|
|
997
1043
|
} catch {
|
|
998
1044
|
warnings.push(`Missing expected directory: ${dir}`);
|
|
@@ -1027,46 +1073,46 @@ async function commitInstallTransaction(input2) {
|
|
|
1027
1073
|
let movedAgentDir = false;
|
|
1028
1074
|
let copiedMemoryDb = false;
|
|
1029
1075
|
try {
|
|
1030
|
-
await
|
|
1031
|
-
await
|
|
1076
|
+
await fs5.mkdir(path4.dirname(input2.workspacePath), { recursive: true });
|
|
1077
|
+
await fs5.rename(input2.stagedWorkspacePath, input2.workspacePath);
|
|
1032
1078
|
movedWorkspace = true;
|
|
1033
|
-
await
|
|
1034
|
-
await
|
|
1079
|
+
await fs5.mkdir(path4.dirname(input2.agentDirPath), { recursive: true });
|
|
1080
|
+
await fs5.rename(input2.stagedAgentDirPath, input2.agentDirPath);
|
|
1035
1081
|
movedAgentDir = true;
|
|
1036
1082
|
if (input2.memoryDbStagedPath && input2.memoryDbTargetPath && await fileExists(input2.memoryDbStagedPath)) {
|
|
1037
|
-
await
|
|
1038
|
-
await
|
|
1083
|
+
await fs5.mkdir(path4.dirname(input2.memoryDbTargetPath), { recursive: true });
|
|
1084
|
+
await fs5.copyFile(input2.memoryDbStagedPath, input2.memoryDbTargetPath);
|
|
1039
1085
|
copiedMemoryDb = true;
|
|
1040
1086
|
}
|
|
1041
1087
|
if (input2.cronContent !== null) {
|
|
1042
|
-
await
|
|
1043
|
-
await
|
|
1088
|
+
await fs5.mkdir(path4.dirname(input2.cronStorePath), { recursive: true });
|
|
1089
|
+
await fs5.writeFile(input2.cronStorePath, input2.cronContent, "utf-8");
|
|
1044
1090
|
}
|
|
1045
|
-
await
|
|
1091
|
+
await fs5.writeFile(input2.configPath, input2.configContent, "utf-8");
|
|
1046
1092
|
} catch (error) {
|
|
1047
1093
|
if (input2.cronContent !== null) {
|
|
1048
1094
|
if (input2.cronExisted && input2.cronBackupContent !== null) {
|
|
1049
|
-
await
|
|
1095
|
+
await fs5.writeFile(input2.cronStorePath, input2.cronBackupContent, "utf-8").catch(() => void 0);
|
|
1050
1096
|
} else {
|
|
1051
|
-
await
|
|
1097
|
+
await fs5.rm(input2.cronStorePath, { force: true }).catch(() => void 0);
|
|
1052
1098
|
}
|
|
1053
1099
|
}
|
|
1054
1100
|
if (copiedMemoryDb && input2.memoryDbTargetPath) {
|
|
1055
|
-
await
|
|
1101
|
+
await fs5.rm(input2.memoryDbTargetPath, { force: true }).catch(() => void 0);
|
|
1056
1102
|
}
|
|
1057
|
-
await
|
|
1103
|
+
await fs5.writeFile(input2.configPath, input2.configBackupContent, "utf-8").catch(() => void 0);
|
|
1058
1104
|
if (movedWorkspace) {
|
|
1059
|
-
await
|
|
1105
|
+
await fs5.rm(input2.workspacePath, { recursive: true, force: true }).catch(() => void 0);
|
|
1060
1106
|
}
|
|
1061
1107
|
if (movedAgentDir) {
|
|
1062
|
-
await
|
|
1108
|
+
await fs5.rm(input2.agentRootPath, { recursive: true, force: true }).catch(() => void 0);
|
|
1063
1109
|
}
|
|
1064
1110
|
const message = error instanceof Error ? error.message : "unknown failure";
|
|
1065
1111
|
throw new Error(`Install failed and was rolled back: ${message}`);
|
|
1066
1112
|
}
|
|
1067
1113
|
}
|
|
1068
1114
|
async function buildOpenClawConfigUnpatch(configPath, agentId) {
|
|
1069
|
-
const raw = await
|
|
1115
|
+
const raw = await fs5.readFile(configPath, "utf-8");
|
|
1070
1116
|
const config = JSON52.parse(raw);
|
|
1071
1117
|
const agents = config.agents ?? {};
|
|
1072
1118
|
if (!Array.isArray(agents.list)) {
|
|
@@ -1089,7 +1135,7 @@ async function buildOpenClawConfigUnpatch(configPath, agentId) {
|
|
|
1089
1135
|
}
|
|
1090
1136
|
async function buildCronJobsUnpatch(cronStorePath, agentId) {
|
|
1091
1137
|
if (!await fileExists(cronStorePath)) return null;
|
|
1092
|
-
const raw = await
|
|
1138
|
+
const raw = await fs5.readFile(cronStorePath, "utf-8");
|
|
1093
1139
|
let parsed;
|
|
1094
1140
|
try {
|
|
1095
1141
|
parsed = JSON52.parse(raw);
|
|
@@ -1135,7 +1181,7 @@ function buildUninstallPlan(context, agentId) {
|
|
|
1135
1181
|
path: agent.workspace,
|
|
1136
1182
|
description: `Workspace directory`
|
|
1137
1183
|
});
|
|
1138
|
-
const agentRootDir =
|
|
1184
|
+
const agentRootDir = path4.dirname(agent.agentDir);
|
|
1139
1185
|
removals.push({
|
|
1140
1186
|
type: "directory",
|
|
1141
1187
|
path: agentRootDir,
|
|
@@ -1160,37 +1206,37 @@ function buildUninstallPlan(context, agentId) {
|
|
|
1160
1206
|
}
|
|
1161
1207
|
async function commitUninstallTransaction(plan) {
|
|
1162
1208
|
try {
|
|
1163
|
-
await
|
|
1209
|
+
await fs5.writeFile(plan.configPath, plan.configPatch.after, "utf-8");
|
|
1164
1210
|
} catch (error) {
|
|
1165
1211
|
const msg = error instanceof Error ? error.message : "unknown failure";
|
|
1166
1212
|
throw new Error(`Uninstall failed while patching openclaw.json: ${msg}`);
|
|
1167
1213
|
}
|
|
1168
1214
|
if (plan.cronPatch) {
|
|
1169
1215
|
try {
|
|
1170
|
-
await
|
|
1216
|
+
await fs5.writeFile(plan.cronStorePath, plan.cronPatch.after, "utf-8");
|
|
1171
1217
|
} catch (error) {
|
|
1172
|
-
await
|
|
1218
|
+
await fs5.writeFile(plan.configPath, plan.configPatch.before, "utf-8").catch(() => void 0);
|
|
1173
1219
|
const msg = error instanceof Error ? error.message : "unknown failure";
|
|
1174
1220
|
throw new Error(`Uninstall failed while patching cron jobs (rolled back openclaw.json): ${msg}`);
|
|
1175
1221
|
}
|
|
1176
1222
|
}
|
|
1177
1223
|
try {
|
|
1178
|
-
await
|
|
1224
|
+
await fs5.rm(plan.agent.workspace, { recursive: true, force: true });
|
|
1179
1225
|
} catch (error) {
|
|
1180
|
-
await
|
|
1226
|
+
await fs5.writeFile(plan.configPath, plan.configPatch.before, "utf-8").catch(() => void 0);
|
|
1181
1227
|
if (plan.cronPatch) {
|
|
1182
|
-
await
|
|
1228
|
+
await fs5.writeFile(plan.cronStorePath, plan.cronPatch.before, "utf-8").catch(() => void 0);
|
|
1183
1229
|
}
|
|
1184
1230
|
const msg = error instanceof Error ? error.message : "unknown failure";
|
|
1185
1231
|
throw new Error(`Uninstall failed while removing workspace (rolled back config files): ${msg}`);
|
|
1186
1232
|
}
|
|
1187
|
-
const agentRootDir =
|
|
1233
|
+
const agentRootDir = path4.dirname(plan.agent.agentDir);
|
|
1188
1234
|
try {
|
|
1189
|
-
await
|
|
1235
|
+
await fs5.rm(agentRootDir, { recursive: true, force: true });
|
|
1190
1236
|
} catch (error) {
|
|
1191
|
-
await
|
|
1237
|
+
await fs5.writeFile(plan.configPath, plan.configPatch.before, "utf-8").catch(() => void 0);
|
|
1192
1238
|
if (plan.cronPatch) {
|
|
1193
|
-
await
|
|
1239
|
+
await fs5.writeFile(plan.cronStorePath, plan.cronPatch.before, "utf-8").catch(() => void 0);
|
|
1194
1240
|
}
|
|
1195
1241
|
const msg = error instanceof Error ? error.message : "unknown failure";
|
|
1196
1242
|
throw new Error(`Uninstall failed while removing agent directory (rolled back config files): ${msg}`);
|
|
@@ -1220,12 +1266,12 @@ function registerConfigCommands(program2) {
|
|
|
1220
1266
|
}
|
|
1221
1267
|
const fileFromHeader = parseContentDispositionFileName(response.headers.get("content-disposition"));
|
|
1222
1268
|
const fallbackFile = `clawmarket-${token}.bin`;
|
|
1223
|
-
const preferredPath =
|
|
1269
|
+
const preferredPath = path4.resolve(fileFromHeader || fallbackFile);
|
|
1224
1270
|
const { outputPath, renamed } = await resolveSafeDownloadPath(preferredPath);
|
|
1225
1271
|
const bytes = Buffer.from(await response.arrayBuffer());
|
|
1226
1272
|
verifyDownloadedArtifactChecksum(bytes, response.headers.get(CLAWMARKET_ARTIFACT_SHA256_HEADER));
|
|
1227
|
-
await
|
|
1228
|
-
await
|
|
1273
|
+
await fs5.mkdir(path4.dirname(outputPath), { recursive: true });
|
|
1274
|
+
await fs5.writeFile(outputPath, bytes);
|
|
1229
1275
|
if (renamed) {
|
|
1230
1276
|
console.log(`Existing file detected, saved as: ${outputPath}`);
|
|
1231
1277
|
}
|
|
@@ -1242,14 +1288,14 @@ function registerConfigCommands(program2) {
|
|
|
1242
1288
|
const server = getDefaultServer();
|
|
1243
1289
|
const context = await loadOpenClawContext();
|
|
1244
1290
|
const artifact = await fetchDownloadArtifact(server, token);
|
|
1245
|
-
const tempRoot = await
|
|
1246
|
-
|
|
1291
|
+
const tempRoot = await fs5.mkdtemp(
|
|
1292
|
+
path4.join(os4.tmpdir(), `.clawmarket-install-${randomUUID().slice(0, 8)}-`)
|
|
1247
1293
|
);
|
|
1248
1294
|
try {
|
|
1249
|
-
const artifactPath =
|
|
1250
|
-
const extractedPath =
|
|
1251
|
-
await
|
|
1252
|
-
await
|
|
1295
|
+
const artifactPath = path4.join(tempRoot, sanitizeFileName(artifact.fileName));
|
|
1296
|
+
const extractedPath = path4.join(tempRoot, "extracted");
|
|
1297
|
+
await fs5.mkdir(extractedPath, { recursive: true });
|
|
1298
|
+
await fs5.writeFile(artifactPath, artifact.bytes);
|
|
1253
1299
|
await extractZipArtifact(artifactPath, extractedPath);
|
|
1254
1300
|
await stripMacOsJunk(extractedPath);
|
|
1255
1301
|
const sourceRoot = await findSourceRoot(extractedPath);
|
|
@@ -1258,8 +1304,8 @@ function registerConfigCommands(program2) {
|
|
|
1258
1304
|
"Downloaded artifact is not an installable bot package (missing source/bot-config.json)."
|
|
1259
1305
|
);
|
|
1260
1306
|
}
|
|
1261
|
-
const sourceBase =
|
|
1262
|
-
const botConfig = await readBotInstallerConfig(
|
|
1307
|
+
const sourceBase = path4.join(sourceRoot, "source");
|
|
1308
|
+
const botConfig = await readBotInstallerConfig(path4.join(sourceBase, "bot-config.json"));
|
|
1263
1309
|
if (typeof botConfig.installerSpecVersion === "number" && botConfig.installerSpecVersion !== 1) {
|
|
1264
1310
|
console.warn(
|
|
1265
1311
|
`Warning: installerSpecVersion=${botConfig.installerSpecVersion} (this CLI currently expects version 1).`
|
|
@@ -1274,8 +1320,8 @@ function registerConfigCommands(program2) {
|
|
|
1274
1320
|
throw new Error(`Agent id "${agentId}" already exists. Choose another id.`);
|
|
1275
1321
|
}
|
|
1276
1322
|
const installPaths = resolveInstallPaths(botConfig, agentId, context.paths.stateDir, sourceBase);
|
|
1277
|
-
const stagedWorkspacePath =
|
|
1278
|
-
const stagedAgentDirPath =
|
|
1323
|
+
const stagedWorkspacePath = path4.join(tempRoot, "staged-workspace");
|
|
1324
|
+
const stagedAgentDirPath = path4.join(tempRoot, "staged-agent");
|
|
1279
1325
|
await copyDirRecursive(installPaths.sourceWorkspace, stagedWorkspacePath);
|
|
1280
1326
|
await copyDirRecursive(installPaths.sourceAgentState, stagedAgentDirPath);
|
|
1281
1327
|
const { configContent, configBackupContent } = await buildOpenClawConfigPatch(
|
|
@@ -1291,6 +1337,20 @@ function registerConfigCommands(program2) {
|
|
|
1291
1337
|
context.paths.cronStorePath,
|
|
1292
1338
|
agentId
|
|
1293
1339
|
);
|
|
1340
|
+
await saveInstallSnapshot({
|
|
1341
|
+
agentId,
|
|
1342
|
+
agentName,
|
|
1343
|
+
openclawConfigBefore: configBackupContent,
|
|
1344
|
+
cronStoreBefore: cronResult?.backupContent ?? null,
|
|
1345
|
+
cronStoreExisted: cronResult?.existed ?? false,
|
|
1346
|
+
installedPaths: {
|
|
1347
|
+
workspace: installPaths.targetWorkspace,
|
|
1348
|
+
agentRoot: installPaths.targetAgentRoot,
|
|
1349
|
+
memoryDb: installPaths.targetMemoryDb ?? null
|
|
1350
|
+
},
|
|
1351
|
+
configPath: context.paths.configPath,
|
|
1352
|
+
cronStorePath: context.paths.cronStorePath
|
|
1353
|
+
});
|
|
1294
1354
|
console.log("\nInstalling...");
|
|
1295
1355
|
await commitInstallTransaction({
|
|
1296
1356
|
stagedWorkspacePath,
|
|
@@ -1309,11 +1369,11 @@ function registerConfigCommands(program2) {
|
|
|
1309
1369
|
memoryDbTargetPath: installPaths.targetMemoryDb
|
|
1310
1370
|
});
|
|
1311
1371
|
await setupApiKeysFromConfig(botConfig, context.paths.configPath, context.paths.envPath);
|
|
1312
|
-
const installerShPath =
|
|
1372
|
+
const installerShPath = path4.join(sourceBase, "installer.sh");
|
|
1313
1373
|
if (await fileExists(installerShPath)) {
|
|
1314
1374
|
console.log("\nRunning post-install hook (installer.sh)...");
|
|
1315
|
-
const hookPath =
|
|
1316
|
-
await
|
|
1375
|
+
const hookPath = path4.join(sourceRoot, "_clawmarket_installer.sh");
|
|
1376
|
+
await fs5.copyFile(installerShPath, hookPath);
|
|
1317
1377
|
await runCommand("bash", [hookPath, context.paths.stateDir], {
|
|
1318
1378
|
cwd: sourceRoot,
|
|
1319
1379
|
stdio: "inherit"
|
|
@@ -1342,7 +1402,7 @@ function registerConfigCommands(program2) {
|
|
|
1342
1402
|
console.log(" 1) openclaw agents list");
|
|
1343
1403
|
console.log(" 2) openclaw gateway restart");
|
|
1344
1404
|
} finally {
|
|
1345
|
-
await
|
|
1405
|
+
await fs5.rm(tempRoot, { recursive: true, force: true }).catch(() => void 0);
|
|
1346
1406
|
}
|
|
1347
1407
|
} catch (error) {
|
|
1348
1408
|
const message = error instanceof Error ? error.message : "Install failed";
|
|
@@ -1379,7 +1439,7 @@ function registerConfigCommands(program2) {
|
|
|
1379
1439
|
console.log(` Uninstalled "${agent.name}" (${agent.id})`);
|
|
1380
1440
|
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
1441
|
console.log(` Removed workspace: ${agent.workspace}`);
|
|
1382
|
-
console.log(` Removed agent dir: ${
|
|
1442
|
+
console.log(` Removed agent dir: ${path4.dirname(agent.agentDir)}`);
|
|
1383
1443
|
console.log(` Removed from openclaw.json agents.list`);
|
|
1384
1444
|
if (cronPatch) {
|
|
1385
1445
|
console.log(` Removed ${cronPatch.removedCount} cron job(s)`);
|
|
@@ -1391,11 +1451,95 @@ function registerConfigCommands(program2) {
|
|
|
1391
1451
|
process.exit(1);
|
|
1392
1452
|
}
|
|
1393
1453
|
});
|
|
1454
|
+
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) => {
|
|
1455
|
+
try {
|
|
1456
|
+
const snapshot = await loadLatestSnapshot();
|
|
1457
|
+
if (!snapshot) {
|
|
1458
|
+
console.log("No recent install snapshot found (snapshots expire after 24 hours).");
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1461
|
+
console.log("");
|
|
1462
|
+
console.log(`Restore to state before installing "${snapshot.agentName}" (${snapshot.agentId})?`);
|
|
1463
|
+
console.log("");
|
|
1464
|
+
console.log(` - Restore openclaw.json`);
|
|
1465
|
+
console.log(` - Remove workspace: ${snapshot.installedPaths.workspace}`);
|
|
1466
|
+
console.log(` - Remove agent directory: ${snapshot.installedPaths.agentRoot}`);
|
|
1467
|
+
if (snapshot.installedPaths.memoryDb) {
|
|
1468
|
+
console.log(` - Remove memory DB: ${snapshot.installedPaths.memoryDb}`);
|
|
1469
|
+
}
|
|
1470
|
+
if (snapshot.cronStoreBefore !== null) {
|
|
1471
|
+
console.log(` - Restore cron jobs`);
|
|
1472
|
+
} else if (!snapshot.cronStoreExisted) {
|
|
1473
|
+
}
|
|
1474
|
+
console.log("");
|
|
1475
|
+
if (!opts.force) {
|
|
1476
|
+
const canPrompt = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
1477
|
+
if (!canPrompt) {
|
|
1478
|
+
throw new Error("Restore requires an interactive terminal for confirmation. Use --force to skip.");
|
|
1479
|
+
}
|
|
1480
|
+
const confirmed = await confirm("Proceed with restore?", false);
|
|
1481
|
+
if (!confirmed) {
|
|
1482
|
+
console.log("Restore cancelled.");
|
|
1483
|
+
return;
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
await fs5.writeFile(snapshot.configPath, snapshot.openclawConfigBefore, "utf-8");
|
|
1487
|
+
if (snapshot.cronStoreBefore !== null) {
|
|
1488
|
+
await fs5.writeFile(snapshot.cronStorePath, snapshot.cronStoreBefore, "utf-8");
|
|
1489
|
+
} else if (!snapshot.cronStoreExisted) {
|
|
1490
|
+
await fs5.rm(snapshot.cronStorePath, { force: true }).catch(() => void 0);
|
|
1491
|
+
}
|
|
1492
|
+
await fs5.rm(snapshot.installedPaths.workspace, { recursive: true, force: true }).catch(() => void 0);
|
|
1493
|
+
await fs5.rm(snapshot.installedPaths.agentRoot, { recursive: true, force: true }).catch(() => void 0);
|
|
1494
|
+
if (snapshot.installedPaths.memoryDb) {
|
|
1495
|
+
await fs5.rm(snapshot.installedPaths.memoryDb, { force: true }).catch(() => void 0);
|
|
1496
|
+
}
|
|
1497
|
+
await deleteSnapshot();
|
|
1498
|
+
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");
|
|
1499
|
+
console.log(` Restored to pre-install state`);
|
|
1500
|
+
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");
|
|
1501
|
+
console.log(` Restored openclaw.json`);
|
|
1502
|
+
console.log(` Removed workspace: ${snapshot.installedPaths.workspace}`);
|
|
1503
|
+
console.log(` Removed agent dir: ${snapshot.installedPaths.agentRoot}`);
|
|
1504
|
+
if (snapshot.installedPaths.memoryDb) {
|
|
1505
|
+
console.log(` Removed memory DB: ${snapshot.installedPaths.memoryDb}`);
|
|
1506
|
+
}
|
|
1507
|
+
if (snapshot.cronStoreBefore !== null) {
|
|
1508
|
+
console.log(` Restored cron jobs`);
|
|
1509
|
+
}
|
|
1510
|
+
console.log("");
|
|
1511
|
+
} catch (error) {
|
|
1512
|
+
const message = error instanceof Error ? error.message : "Restore failed";
|
|
1513
|
+
console.error(message);
|
|
1514
|
+
process.exit(1);
|
|
1515
|
+
}
|
|
1516
|
+
});
|
|
1517
|
+
configCommand.command("snapshots").description("List available install snapshots").action(async () => {
|
|
1518
|
+
try {
|
|
1519
|
+
const snapshot = await loadLatestSnapshot();
|
|
1520
|
+
if (!snapshot) {
|
|
1521
|
+
console.log("No install snapshots available.");
|
|
1522
|
+
return;
|
|
1523
|
+
}
|
|
1524
|
+
const created = new Date(snapshot.createdAt);
|
|
1525
|
+
const expires = new Date(created.getTime() + 24 * 60 * 60 * 1e3);
|
|
1526
|
+
const formatDate = (d) => d.toISOString().replace("T", " ").replace(/\.\d{3}Z$/, "");
|
|
1527
|
+
console.log("");
|
|
1528
|
+
console.log(` Agent: ${snapshot.agentId} (${snapshot.agentName})`);
|
|
1529
|
+
console.log(` Created: ${formatDate(created)}`);
|
|
1530
|
+
console.log(` Expires: ${formatDate(expires)}`);
|
|
1531
|
+
console.log("");
|
|
1532
|
+
} catch (error) {
|
|
1533
|
+
const message = error instanceof Error ? error.message : "Failed to list snapshots";
|
|
1534
|
+
console.error(message);
|
|
1535
|
+
process.exit(1);
|
|
1536
|
+
}
|
|
1537
|
+
});
|
|
1394
1538
|
}
|
|
1395
1539
|
|
|
1396
1540
|
// src/index.ts
|
|
1397
1541
|
var program = new Command();
|
|
1398
|
-
program.name("clawmarketbot").description("CLI tool for ClawMarket - discover, download, and install OpenClaw configs").version("0.1.
|
|
1542
|
+
program.name("clawmarketbot").description("CLI tool for ClawMarket - discover, download, and install OpenClaw configs").version("0.1.9");
|
|
1399
1543
|
registerAuthCommands(program);
|
|
1400
1544
|
registerConfigCommands(program);
|
|
1401
1545
|
program.parse();
|