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.
Files changed (2) hide show
  1. package/dist/index.js +231 -87
  2. 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 fs4 from "fs/promises";
216
- import path3 from "path";
217
- import os3 from "os";
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 = path3.resolve(targetPath);
646
+ const resolved = path4.resolve(targetPath);
601
647
  if (!await fileExists(resolved)) {
602
648
  return { outputPath: resolved, renamed: false };
603
649
  }
604
- const parsed = path3.parse(resolved);
650
+ const parsed = path4.parse(resolved);
605
651
  for (let i = 1; i <= 1e3; i += 1) {
606
- const candidate = path3.join(parsed.dir, `${parsed.name}-${i}${parsed.ext}`);
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 = path3.basename(value || "").replace(/[^A-Za-z0-9._-]/g, "-");
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 fs4.readFile(configPath, "utf-8");
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 = path3.join(stateDir, applyTemplate(tplAgentState));
817
+ const targetAgentRoot = path4.join(stateDir, applyTemplate(tplAgentState));
772
818
  return {
773
- sourceWorkspace: path3.join(sourceBase, srcWorkspace),
774
- sourceAgentState: path3.join(sourceBase, srcAgentState),
775
- sourceMemoryDb: srcMemoryDb ? path3.join(sourceBase, srcMemoryDb) : null,
776
- sourceCronJobs: srcCronJobs ? path3.join(sourceBase, srcCronJobs) : null,
777
- targetWorkspace: path3.join(stateDir, applyTemplate(tplWorkspace)),
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: path3.join(targetAgentRoot, "agent"),
780
- targetMemoryDb: tplMemoryDb ? path3.join(stateDir, applyTemplate(tplMemoryDb)) : null
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 = [path3.resolve(extractedDir)];
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(path3.join(current, "source", "bot-config.json"))) {
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 fs4.readdir(current, { withFileTypes: true });
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(path3.join(current, entry.name));
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 fs4.mkdir(dest, { recursive: true });
808
- const entries = await fs4.readdir(src, { withFileTypes: true });
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 = path3.join(src, entry.name);
811
- const destPath = path3.join(dest, entry.name);
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 fs4.copyFile(srcPath, destPath);
861
+ await fs5.copyFile(srcPath, destPath);
816
862
  }
817
863
  }
818
864
  }
819
865
  async function stripMacOsJunk(dir) {
820
- const macosDir = path3.join(dir, "__MACOSX");
821
- await fs4.rm(macosDir, { recursive: true, force: true }).catch(() => void 0);
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 fs4.readdir(d, { withFileTypes: true });
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 = path3.join(d, entry.name);
876
+ const full = path4.join(d, entry.name);
831
877
  if (entry.isFile() && entry.name === ".DS_Store") {
832
- await fs4.rm(full, { force: true }).catch(() => void 0);
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 fs4.readFile(configPath, "utf-8");
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 fs4.readFile(sourceCronPath, "utf-8");
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 fs4.readFile(cronStorePath, "utf-8");
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 fs4.readFile(configPath, "utf-8");
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 fs4.writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
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 fs4.mkdir(path3.dirname(envPath), { recursive: true });
1001
+ await fs5.mkdir(path4.dirname(envPath), { recursive: true });
956
1002
  let envContent = "";
957
1003
  try {
958
- envContent = await fs4.readFile(envPath, "utf-8");
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 fs4.writeFile(envPath, envContent.trim() + "\n", "utf-8");
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(path3.join(workspacePath, file))) {
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 fs4.stat(path3.join(workspacePath, dir));
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 fs4.mkdir(path3.dirname(input2.workspacePath), { recursive: true });
1031
- await fs4.rename(input2.stagedWorkspacePath, input2.workspacePath);
1076
+ await fs5.mkdir(path4.dirname(input2.workspacePath), { recursive: true });
1077
+ await fs5.rename(input2.stagedWorkspacePath, input2.workspacePath);
1032
1078
  movedWorkspace = true;
1033
- await fs4.mkdir(path3.dirname(input2.agentDirPath), { recursive: true });
1034
- await fs4.rename(input2.stagedAgentDirPath, input2.agentDirPath);
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 fs4.mkdir(path3.dirname(input2.memoryDbTargetPath), { recursive: true });
1038
- await fs4.copyFile(input2.memoryDbStagedPath, input2.memoryDbTargetPath);
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 fs4.mkdir(path3.dirname(input2.cronStorePath), { recursive: true });
1043
- await fs4.writeFile(input2.cronStorePath, input2.cronContent, "utf-8");
1088
+ await fs5.mkdir(path4.dirname(input2.cronStorePath), { recursive: true });
1089
+ await fs5.writeFile(input2.cronStorePath, input2.cronContent, "utf-8");
1044
1090
  }
1045
- await fs4.writeFile(input2.configPath, input2.configContent, "utf-8");
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 fs4.writeFile(input2.cronStorePath, input2.cronBackupContent, "utf-8").catch(() => void 0);
1095
+ await fs5.writeFile(input2.cronStorePath, input2.cronBackupContent, "utf-8").catch(() => void 0);
1050
1096
  } else {
1051
- await fs4.rm(input2.cronStorePath, { force: true }).catch(() => void 0);
1097
+ await fs5.rm(input2.cronStorePath, { force: true }).catch(() => void 0);
1052
1098
  }
1053
1099
  }
1054
1100
  if (copiedMemoryDb && input2.memoryDbTargetPath) {
1055
- await fs4.rm(input2.memoryDbTargetPath, { force: true }).catch(() => void 0);
1101
+ await fs5.rm(input2.memoryDbTargetPath, { force: true }).catch(() => void 0);
1056
1102
  }
1057
- await fs4.writeFile(input2.configPath, input2.configBackupContent, "utf-8").catch(() => void 0);
1103
+ await fs5.writeFile(input2.configPath, input2.configBackupContent, "utf-8").catch(() => void 0);
1058
1104
  if (movedWorkspace) {
1059
- await fs4.rm(input2.workspacePath, { recursive: true, force: true }).catch(() => void 0);
1105
+ await fs5.rm(input2.workspacePath, { recursive: true, force: true }).catch(() => void 0);
1060
1106
  }
1061
1107
  if (movedAgentDir) {
1062
- await fs4.rm(input2.agentRootPath, { recursive: true, force: true }).catch(() => void 0);
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 fs4.readFile(configPath, "utf-8");
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 fs4.readFile(cronStorePath, "utf-8");
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 = path3.dirname(agent.agentDir);
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 fs4.writeFile(plan.configPath, plan.configPatch.after, "utf-8");
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 fs4.writeFile(plan.cronStorePath, plan.cronPatch.after, "utf-8");
1216
+ await fs5.writeFile(plan.cronStorePath, plan.cronPatch.after, "utf-8");
1171
1217
  } catch (error) {
1172
- await fs4.writeFile(plan.configPath, plan.configPatch.before, "utf-8").catch(() => void 0);
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 fs4.rm(plan.agent.workspace, { recursive: true, force: true });
1224
+ await fs5.rm(plan.agent.workspace, { recursive: true, force: true });
1179
1225
  } catch (error) {
1180
- await fs4.writeFile(plan.configPath, plan.configPatch.before, "utf-8").catch(() => void 0);
1226
+ await fs5.writeFile(plan.configPath, plan.configPatch.before, "utf-8").catch(() => void 0);
1181
1227
  if (plan.cronPatch) {
1182
- await fs4.writeFile(plan.cronStorePath, plan.cronPatch.before, "utf-8").catch(() => void 0);
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 = path3.dirname(plan.agent.agentDir);
1233
+ const agentRootDir = path4.dirname(plan.agent.agentDir);
1188
1234
  try {
1189
- await fs4.rm(agentRootDir, { recursive: true, force: true });
1235
+ await fs5.rm(agentRootDir, { recursive: true, force: true });
1190
1236
  } catch (error) {
1191
- await fs4.writeFile(plan.configPath, plan.configPatch.before, "utf-8").catch(() => void 0);
1237
+ await fs5.writeFile(plan.configPath, plan.configPatch.before, "utf-8").catch(() => void 0);
1192
1238
  if (plan.cronPatch) {
1193
- await fs4.writeFile(plan.cronStorePath, plan.cronPatch.before, "utf-8").catch(() => void 0);
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 = path3.resolve(fileFromHeader || fallbackFile);
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 fs4.mkdir(path3.dirname(outputPath), { recursive: true });
1228
- await fs4.writeFile(outputPath, bytes);
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 fs4.mkdtemp(
1246
- path3.join(os3.tmpdir(), `.clawmarket-install-${randomUUID().slice(0, 8)}-`)
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 = path3.join(tempRoot, sanitizeFileName(artifact.fileName));
1250
- const extractedPath = path3.join(tempRoot, "extracted");
1251
- await fs4.mkdir(extractedPath, { recursive: true });
1252
- await fs4.writeFile(artifactPath, artifact.bytes);
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 = path3.join(sourceRoot, "source");
1262
- const botConfig = await readBotInstallerConfig(path3.join(sourceBase, "bot-config.json"));
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 = path3.join(tempRoot, "staged-workspace");
1278
- const stagedAgentDirPath = path3.join(tempRoot, "staged-agent");
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 = path3.join(sourceBase, "installer.sh");
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 = path3.join(sourceRoot, "_clawmarket_installer.sh");
1316
- await fs4.copyFile(installerShPath, hookPath);
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 fs4.rm(tempRoot, { recursive: true, force: true }).catch(() => void 0);
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: ${path3.dirname(agent.agentDir)}`);
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.8");
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawmarketbot",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "private": false,
5
5
  "description": "CLI tool for ClawMarket - discover, download, and install OpenClaw configs",
6
6
  "type": "module",