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.
Files changed (2) hide show
  1. package/dist/index.js +252 -93
  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) {
@@ -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 fs4.readFile(configPath, "utf-8");
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 = path3.join(stateDir, applyTemplate(tplAgentState));
818
+ const targetAgentRoot = path4.join(stateDir, applyTemplate(tplAgentState));
772
819
  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)),
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: path3.join(targetAgentRoot, "agent"),
780
- targetMemoryDb: tplMemoryDb ? path3.join(stateDir, applyTemplate(tplMemoryDb)) : null
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 = [path3.resolve(extractedDir)];
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(path3.join(current, "source", "bot-config.json"))) {
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 fs4.readdir(current, { withFileTypes: true });
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(path3.join(current, entry.name));
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 fs4.mkdir(dest, { recursive: true });
808
- const entries = await fs4.readdir(src, { withFileTypes: true });
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 = path3.join(src, entry.name);
811
- const destPath = path3.join(dest, entry.name);
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 fs4.copyFile(srcPath, destPath);
862
+ await fs5.copyFile(srcPath, destPath);
816
863
  }
817
864
  }
818
865
  }
819
866
  async function stripMacOsJunk(dir) {
820
- const macosDir = path3.join(dir, "__MACOSX");
821
- await fs4.rm(macosDir, { recursive: true, force: true }).catch(() => void 0);
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 fs4.readdir(d, { withFileTypes: true });
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 = path3.join(d, entry.name);
877
+ const full = path4.join(d, entry.name);
831
878
  if (entry.isFile() && entry.name === ".DS_Store") {
832
- await fs4.rm(full, { force: true }).catch(() => void 0);
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 fs4.readFile(configPath, "utf-8");
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 fs4.readFile(sourceCronPath, "utf-8");
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 fs4.readFile(cronStorePath, "utf-8");
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 fs4.readFile(configPath, "utf-8");
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 fs4.writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
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 fs4.mkdir(path3.dirname(envPath), { recursive: true });
1002
+ await fs5.mkdir(path4.dirname(envPath), { recursive: true });
956
1003
  let envContent = "";
957
1004
  try {
958
- envContent = await fs4.readFile(envPath, "utf-8");
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 fs4.writeFile(envPath, envContent.trim() + "\n", "utf-8");
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(path3.join(workspacePath, file))) {
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 fs4.stat(path3.join(workspacePath, dir));
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 fs4.mkdir(path3.dirname(input2.workspacePath), { recursive: true });
1031
- await fs4.rename(input2.stagedWorkspacePath, input2.workspacePath);
1077
+ await fs5.mkdir(path4.dirname(input2.workspacePath), { recursive: true });
1078
+ await fs5.rename(input2.stagedWorkspacePath, input2.workspacePath);
1032
1079
  movedWorkspace = true;
1033
- await fs4.mkdir(path3.dirname(input2.agentDirPath), { recursive: true });
1034
- await fs4.rename(input2.stagedAgentDirPath, input2.agentDirPath);
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 fs4.mkdir(path3.dirname(input2.memoryDbTargetPath), { recursive: true });
1038
- await fs4.copyFile(input2.memoryDbStagedPath, input2.memoryDbTargetPath);
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 fs4.mkdir(path3.dirname(input2.cronStorePath), { recursive: true });
1043
- await fs4.writeFile(input2.cronStorePath, input2.cronContent, "utf-8");
1089
+ await fs5.mkdir(path4.dirname(input2.cronStorePath), { recursive: true });
1090
+ await fs5.writeFile(input2.cronStorePath, input2.cronContent, "utf-8");
1044
1091
  }
1045
- await fs4.writeFile(input2.configPath, input2.configContent, "utf-8");
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 fs4.writeFile(input2.cronStorePath, input2.cronBackupContent, "utf-8").catch(() => void 0);
1096
+ await fs5.writeFile(input2.cronStorePath, input2.cronBackupContent, "utf-8").catch(() => void 0);
1050
1097
  } else {
1051
- await fs4.rm(input2.cronStorePath, { force: true }).catch(() => void 0);
1098
+ await fs5.rm(input2.cronStorePath, { force: true }).catch(() => void 0);
1052
1099
  }
1053
1100
  }
1054
1101
  if (copiedMemoryDb && input2.memoryDbTargetPath) {
1055
- await fs4.rm(input2.memoryDbTargetPath, { force: true }).catch(() => void 0);
1102
+ await fs5.rm(input2.memoryDbTargetPath, { force: true }).catch(() => void 0);
1056
1103
  }
1057
- await fs4.writeFile(input2.configPath, input2.configBackupContent, "utf-8").catch(() => void 0);
1104
+ await fs5.writeFile(input2.configPath, input2.configBackupContent, "utf-8").catch(() => void 0);
1058
1105
  if (movedWorkspace) {
1059
- await fs4.rm(input2.workspacePath, { recursive: true, force: true }).catch(() => void 0);
1106
+ await fs5.rm(input2.workspacePath, { recursive: true, force: true }).catch(() => void 0);
1060
1107
  }
1061
1108
  if (movedAgentDir) {
1062
- await fs4.rm(input2.agentRootPath, { recursive: true, force: true }).catch(() => void 0);
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 fs4.readFile(configPath, "utf-8");
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 fs4.readFile(cronStorePath, "utf-8");
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 = path3.dirname(agent.agentDir);
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 fs4.writeFile(plan.configPath, plan.configPatch.after, "utf-8");
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 fs4.writeFile(plan.cronStorePath, plan.cronPatch.after, "utf-8");
1217
+ await fs5.writeFile(plan.cronStorePath, plan.cronPatch.after, "utf-8");
1171
1218
  } catch (error) {
1172
- await fs4.writeFile(plan.configPath, plan.configPatch.before, "utf-8").catch(() => void 0);
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 fs4.rm(plan.agent.workspace, { recursive: true, force: true });
1225
+ await fs5.rm(plan.agent.workspace, { recursive: true, force: true });
1179
1226
  } catch (error) {
1180
- await fs4.writeFile(plan.configPath, plan.configPatch.before, "utf-8").catch(() => void 0);
1227
+ await fs5.writeFile(plan.configPath, plan.configPatch.before, "utf-8").catch(() => void 0);
1181
1228
  if (plan.cronPatch) {
1182
- await fs4.writeFile(plan.cronStorePath, plan.cronPatch.before, "utf-8").catch(() => void 0);
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 = path3.dirname(plan.agent.agentDir);
1234
+ const agentRootDir = path4.dirname(plan.agent.agentDir);
1188
1235
  try {
1189
- await fs4.rm(agentRootDir, { recursive: true, force: true });
1236
+ await fs5.rm(agentRootDir, { recursive: true, force: true });
1190
1237
  } catch (error) {
1191
- await fs4.writeFile(plan.configPath, plan.configPatch.before, "utf-8").catch(() => void 0);
1238
+ await fs5.writeFile(plan.configPath, plan.configPatch.before, "utf-8").catch(() => void 0);
1192
1239
  if (plan.cronPatch) {
1193
- await fs4.writeFile(plan.cronStorePath, plan.cronPatch.before, "utf-8").catch(() => void 0);
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 = path3.resolve(fileFromHeader || fallbackFile);
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 fs4.mkdir(path3.dirname(outputPath), { recursive: true });
1228
- await fs4.writeFile(outputPath, bytes);
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 fs4.mkdtemp(
1246
- path3.join(os3.tmpdir(), `.clawmarket-install-${randomUUID().slice(0, 8)}-`)
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 = 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);
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 = path3.join(sourceRoot, "source");
1262
- const botConfig = await readBotInstallerConfig(path3.join(sourceBase, "bot-config.json"));
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 = path3.join(tempRoot, "staged-workspace");
1278
- const stagedAgentDirPath = path3.join(tempRoot, "staged-agent");
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 = path3.join(sourceBase, "installer.sh");
1373
+ const installerShPath = path4.join(sourceBase, "installer.sh");
1313
1374
  if (await fileExists(installerShPath)) {
1314
- console.log("\nRunning post-install hook (installer.sh)...");
1315
- const hookPath = path3.join(sourceRoot, "_clawmarket_installer.sh");
1316
- await fs4.copyFile(installerShPath, hookPath);
1317
- await runCommand("bash", [hookPath, context.paths.stateDir], {
1318
- cwd: sourceRoot,
1319
- stdio: "inherit"
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 fs4.rm(tempRoot, { recursive: true, force: true }).catch(() => void 0);
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: ${path3.dirname(agent.agentDir)}`);
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.8");
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawmarketbot",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "private": false,
5
5
  "description": "CLI tool for ClawMarket - discover, download, and install OpenClaw configs",
6
6
  "type": "module",