clawon 0.1.2 → 0.1.3

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 +155 -1
  2. package/package.json +8 -2
package/dist/index.js CHANGED
@@ -5,9 +5,11 @@ import { Command } from "commander";
5
5
  import fs from "fs";
6
6
  import path from "path";
7
7
  import os from "os";
8
+ import zlib from "zlib";
8
9
  var CONFIG_DIR = path.join(os.homedir(), ".clawon");
9
10
  var CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
10
11
  var OPENCLAW_DIR = path.join(os.homedir(), ".openclaw");
12
+ var BACKUPS_DIR = path.join(CONFIG_DIR, "backups");
11
13
  function ensureDir(dir) {
12
14
  fs.mkdirSync(dir, { recursive: true });
13
15
  }
@@ -91,6 +93,39 @@ function discoverFiles(baseDir) {
91
93
  walk(baseDir);
92
94
  return files;
93
95
  }
96
+ function createLocalArchive(files, openclawDir) {
97
+ const archiveFiles = files.map((f) => {
98
+ const fullPath = path.join(openclawDir, f.path);
99
+ const content = fs.readFileSync(fullPath).toString("base64");
100
+ return { path: f.path, size: f.size, content };
101
+ });
102
+ const archive = {
103
+ version: 1,
104
+ created: (/* @__PURE__ */ new Date()).toISOString(),
105
+ files: archiveFiles
106
+ };
107
+ return zlib.gzipSync(JSON.stringify(archive));
108
+ }
109
+ function extractLocalArchive(archivePath) {
110
+ const compressed = fs.readFileSync(archivePath);
111
+ const json = zlib.gunzipSync(compressed).toString("utf8");
112
+ const archive = JSON.parse(json);
113
+ return { created: archive.created, files: archive.files };
114
+ }
115
+ var POSTHOG_KEY = "phc_LGJC4ZrED6EiK0sC1fusErOhR6gHlFCS5Qs7ou93SmV";
116
+ function trackCliEvent(distinctId, event, properties = {}) {
117
+ fetch("https://us.i.posthog.com/capture/", {
118
+ method: "POST",
119
+ headers: { "content-type": "application/json" },
120
+ body: JSON.stringify({
121
+ api_key: POSTHOG_KEY,
122
+ distinct_id: distinctId,
123
+ event,
124
+ properties: { ...properties, source: "cli" }
125
+ })
126
+ }).catch(() => {
127
+ });
128
+ }
94
129
  var program = new Command();
95
130
  program.name("clawon").description("Backup and restore your OpenClaw workspace").version("0.1.1");
96
131
  program.command("login").description("Connect to Clawon with your API key").requiredOption("--api-key <key>", "Your Clawon API key").option("--api-url <url>", "API base URL", "https://clawon.io").action(async (opts) => {
@@ -182,10 +217,14 @@ program.command("backup").description("Backup your OpenClaw workspace to the clo
182
217
  console.log(` Snapshot ID: ${snapshotId}`);
183
218
  console.log(` Files: ${files.length}`);
184
219
  console.log(` Size: ${(totalSize / 1024).toFixed(1)} KB`);
220
+ trackCliEvent(cfg.profileId, "cloud_backup_created", {
221
+ file_count: files.length,
222
+ total_bytes: totalSize
223
+ });
185
224
  } catch (e) {
186
225
  const msg = e.message;
187
226
  if (msg.includes("Snapshot limit")) {
188
- console.error("\n\u2717 Snapshot limit reached (5/5).");
227
+ console.error("\n\u2717 Snapshot limit reached (2).");
189
228
  console.error(" Delete one first: clawon delete <id>");
190
229
  console.error(" Delete oldest: clawon delete --oldest");
191
230
  console.error(" List snapshots: clawon list");
@@ -245,6 +284,9 @@ program.command("restore").description("Restore your OpenClaw workspace from the
245
284
  console.log("\n\u2713 Restore complete!");
246
285
  console.log(` Restored to: ${OPENCLAW_DIR}`);
247
286
  console.log(` Files: ${files.length}`);
287
+ trackCliEvent(cfg.profileId, "cloud_backup_restored", {
288
+ file_count: files.length
289
+ });
248
290
  } catch (e) {
249
291
  console.error(`
250
292
  \u2717 Restore failed: ${e.message}`);
@@ -420,6 +462,118 @@ Total: ${files.length} files`);
420
462
  process.exit(1);
421
463
  }
422
464
  });
465
+ var local = program.command("local").description("Local backup and restore (no cloud required)");
466
+ local.command("backup").description("Save a local backup of your OpenClaw workspace").action(async () => {
467
+ if (!fs.existsSync(OPENCLAW_DIR)) {
468
+ console.error(`\u2717 OpenClaw directory not found: ${OPENCLAW_DIR}`);
469
+ process.exit(1);
470
+ }
471
+ console.log("Discovering files...");
472
+ const files = discoverFiles(OPENCLAW_DIR);
473
+ if (files.length === 0) {
474
+ console.error("\u2717 No files found to backup");
475
+ process.exit(1);
476
+ }
477
+ const totalSize = files.reduce((sum, f) => sum + f.size, 0);
478
+ console.log(`Found ${files.length} files (${(totalSize / 1024).toFixed(1)} KB)`);
479
+ console.log("Creating archive...");
480
+ const archive = createLocalArchive(files, OPENCLAW_DIR);
481
+ ensureDir(BACKUPS_DIR);
482
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "").replace("T", "T").slice(0, 15);
483
+ const filename = `backup-${timestamp}.tar.gz`;
484
+ const filePath = path.join(BACKUPS_DIR, filename);
485
+ fs.writeFileSync(filePath, archive);
486
+ console.log(`
487
+ \u2713 Local backup saved!`);
488
+ console.log(` File: ${filePath}`);
489
+ console.log(` Files: ${files.length}`);
490
+ console.log(` Size: ${(archive.length / 1024).toFixed(1)} KB (compressed)`);
491
+ const cfg = readConfig();
492
+ trackCliEvent(cfg?.profileId || "anonymous", "local_backup_created", {
493
+ file_count: files.length,
494
+ total_bytes: totalSize
495
+ });
496
+ });
497
+ local.command("list").description("List local backups").action(async () => {
498
+ if (!fs.existsSync(BACKUPS_DIR)) {
499
+ console.log("No local backups yet. Run: clawon local backup");
500
+ return;
501
+ }
502
+ const entries = fs.readdirSync(BACKUPS_DIR).filter((f) => f.endsWith(".tar.gz")).sort().reverse();
503
+ if (entries.length === 0) {
504
+ console.log("No local backups yet. Run: clawon local backup");
505
+ return;
506
+ }
507
+ console.log("Local backups:\n");
508
+ console.log("# | Date | Files | Size");
509
+ console.log("\u2500".repeat(60));
510
+ for (let i = 0; i < entries.length; i++) {
511
+ const filePath = path.join(BACKUPS_DIR, entries[i]);
512
+ try {
513
+ const { created, files } = extractLocalArchive(filePath);
514
+ const totalSize = files.reduce((sum, f) => sum + f.size, 0);
515
+ const date = new Date(created).toLocaleString();
516
+ console.log(
517
+ `${String(i + 1).padStart(2)} | ${date.padEnd(25)} | ${String(files.length).padEnd(5)} | ${(totalSize / 1024).toFixed(1)} KB`
518
+ );
519
+ } catch {
520
+ console.log(`${String(i + 1).padStart(2)} | ${entries[i].padEnd(25)} | ??? | ???`);
521
+ }
522
+ }
523
+ console.log(`
524
+ Total: ${entries.length} backup(s)`);
525
+ console.log(`Location: ${BACKUPS_DIR}`);
526
+ });
527
+ local.command("restore").description("Restore from a local backup").option("--file <path>", "Path to an external backup file").action(async (opts) => {
528
+ let archivePath;
529
+ if (opts.file) {
530
+ archivePath = path.resolve(opts.file);
531
+ if (!fs.existsSync(archivePath)) {
532
+ console.error(`\u2717 File not found: ${archivePath}`);
533
+ process.exit(1);
534
+ }
535
+ } else {
536
+ if (!fs.existsSync(BACKUPS_DIR)) {
537
+ console.error("\u2717 No local backups found. Run: clawon local backup");
538
+ process.exit(1);
539
+ }
540
+ const entries = fs.readdirSync(BACKUPS_DIR).filter((f) => f.endsWith(".tar.gz")).sort().reverse();
541
+ if (entries.length === 0) {
542
+ console.error("\u2717 No local backups found. Run: clawon local backup");
543
+ process.exit(1);
544
+ }
545
+ archivePath = path.join(BACKUPS_DIR, entries[0]);
546
+ }
547
+ console.log(`Restoring from: ${archivePath}`);
548
+ try {
549
+ const { created, files } = extractLocalArchive(archivePath);
550
+ const totalSize = files.reduce((sum, f) => sum + f.size, 0);
551
+ console.log(`Backup date: ${new Date(created).toLocaleString()}`);
552
+ console.log(`Files: ${files.length} (${(totalSize / 1024).toFixed(1)} KB)`);
553
+ let restored = 0;
554
+ for (const file of files) {
555
+ const targetPath = path.join(OPENCLAW_DIR, file.path);
556
+ ensureDir(path.dirname(targetPath));
557
+ fs.writeFileSync(targetPath, Buffer.from(file.content, "base64"));
558
+ restored++;
559
+ process.stdout.write(`\r Restored: ${restored}/${files.length}`);
560
+ }
561
+ console.log("");
562
+ console.log(`
563
+ \u2713 Restore complete!`);
564
+ console.log(` Restored to: ${OPENCLAW_DIR}`);
565
+ console.log(` Files: ${files.length}`);
566
+ const cfg = readConfig();
567
+ trackCliEvent(cfg?.profileId || "anonymous", "local_backup_restored", {
568
+ file_count: files.length,
569
+ source: opts.file ? "file" : "local"
570
+ });
571
+ } catch (e) {
572
+ console.error(`
573
+ \u2717 Restore failed: ${e.message}`);
574
+ process.exit(1);
575
+ }
576
+ });
423
577
  program.command("status").description("Show current status").action(async () => {
424
578
  const cfg = readConfig();
425
579
  console.log("Clawon Status\n");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawon",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Backup and restore your OpenClaw workspace",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,7 +15,13 @@
15
15
  "prepublishOnly": "npm run build",
16
16
  "start": "tsx src/index.ts"
17
17
  },
18
- "keywords": ["openclaw", "clawon", "backup", "restore", "cli"],
18
+ "keywords": [
19
+ "openclaw",
20
+ "clawon",
21
+ "backup",
22
+ "restore",
23
+ "cli"
24
+ ],
19
25
  "license": "MIT",
20
26
  "dependencies": {
21
27
  "commander": "^12.1.0"