clawon 0.1.2 → 0.1.4
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 +168 -1
- 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 (
|
|
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,131 @@ 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 | Path");
|
|
509
|
+
console.log("\u2500".repeat(100));
|
|
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).padEnd(8)} KB | ${filePath}`
|
|
518
|
+
);
|
|
519
|
+
} catch {
|
|
520
|
+
console.log(`${String(i + 1).padStart(2)} | ${entries[i].padEnd(25)} | ??? | ??? | ${filePath}`);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
console.log(`
|
|
524
|
+
Total: ${entries.length} backup(s)`);
|
|
525
|
+
console.log(`
|
|
526
|
+
Restore a backup:`);
|
|
527
|
+
console.log(` clawon local restore Restore the latest backup (#1)`);
|
|
528
|
+
console.log(` clawon local restore --pick 2 Restore backup #2 from this list`);
|
|
529
|
+
console.log(` clawon local restore --file <path> Restore from an external file`);
|
|
530
|
+
});
|
|
531
|
+
local.command("restore").description("Restore from a local backup").option("--file <path>", "Path to an external backup file").option("--pick <n>", 'Restore backup #n from "clawon local list"').action(async (opts) => {
|
|
532
|
+
let archivePath;
|
|
533
|
+
if (opts.file) {
|
|
534
|
+
archivePath = path.resolve(opts.file);
|
|
535
|
+
if (!fs.existsSync(archivePath)) {
|
|
536
|
+
console.error(`\u2717 File not found: ${archivePath}`);
|
|
537
|
+
process.exit(1);
|
|
538
|
+
}
|
|
539
|
+
} else {
|
|
540
|
+
if (!fs.existsSync(BACKUPS_DIR)) {
|
|
541
|
+
console.error("\u2717 No local backups found. Run: clawon local backup");
|
|
542
|
+
process.exit(1);
|
|
543
|
+
}
|
|
544
|
+
const entries = fs.readdirSync(BACKUPS_DIR).filter((f) => f.endsWith(".tar.gz")).sort().reverse();
|
|
545
|
+
if (entries.length === 0) {
|
|
546
|
+
console.error("\u2717 No local backups found. Run: clawon local backup");
|
|
547
|
+
process.exit(1);
|
|
548
|
+
}
|
|
549
|
+
if (opts.pick) {
|
|
550
|
+
const idx = parseInt(opts.pick, 10) - 1;
|
|
551
|
+
if (isNaN(idx) || idx < 0 || idx >= entries.length) {
|
|
552
|
+
console.error(`\u2717 Invalid pick: #${opts.pick}. Run "clawon local list" to see available backups (1-${entries.length}).`);
|
|
553
|
+
process.exit(1);
|
|
554
|
+
}
|
|
555
|
+
archivePath = path.join(BACKUPS_DIR, entries[idx]);
|
|
556
|
+
} else {
|
|
557
|
+
archivePath = path.join(BACKUPS_DIR, entries[0]);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
console.log(`Restoring from: ${archivePath}`);
|
|
561
|
+
try {
|
|
562
|
+
const { created, files } = extractLocalArchive(archivePath);
|
|
563
|
+
const totalSize = files.reduce((sum, f) => sum + f.size, 0);
|
|
564
|
+
console.log(`Backup date: ${new Date(created).toLocaleString()}`);
|
|
565
|
+
console.log(`Files: ${files.length} (${(totalSize / 1024).toFixed(1)} KB)`);
|
|
566
|
+
let restored = 0;
|
|
567
|
+
for (const file of files) {
|
|
568
|
+
const targetPath = path.join(OPENCLAW_DIR, file.path);
|
|
569
|
+
ensureDir(path.dirname(targetPath));
|
|
570
|
+
fs.writeFileSync(targetPath, Buffer.from(file.content, "base64"));
|
|
571
|
+
restored++;
|
|
572
|
+
process.stdout.write(`\r Restored: ${restored}/${files.length}`);
|
|
573
|
+
}
|
|
574
|
+
console.log("");
|
|
575
|
+
console.log(`
|
|
576
|
+
\u2713 Restore complete!`);
|
|
577
|
+
console.log(` Restored to: ${OPENCLAW_DIR}`);
|
|
578
|
+
console.log(` Files: ${files.length}`);
|
|
579
|
+
const cfg = readConfig();
|
|
580
|
+
trackCliEvent(cfg?.profileId || "anonymous", "local_backup_restored", {
|
|
581
|
+
file_count: files.length,
|
|
582
|
+
source: opts.file ? "file" : "local"
|
|
583
|
+
});
|
|
584
|
+
} catch (e) {
|
|
585
|
+
console.error(`
|
|
586
|
+
\u2717 Restore failed: ${e.message}`);
|
|
587
|
+
process.exit(1);
|
|
588
|
+
}
|
|
589
|
+
});
|
|
423
590
|
program.command("status").description("Show current status").action(async () => {
|
|
424
591
|
const cfg = readConfig();
|
|
425
592
|
console.log("Clawon Status\n");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clawon",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
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": [
|
|
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"
|