clawon 0.1.10 → 0.1.12
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/README.md +32 -6
- package/dist/index.js +199 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,6 +22,7 @@ Local backups are stored in `~/.clawon/backups/` as standard `.tar.gz` archives.
|
|
|
22
22
|
npx clawon local backup
|
|
23
23
|
npx clawon local backup --tag "before migration"
|
|
24
24
|
npx clawon local backup --include-memory-db # Include SQLite memory index
|
|
25
|
+
npx clawon local backup --include-sessions # Include chat history
|
|
25
26
|
npx clawon local backup --max-snapshots 10 # Keep only 10 most recent
|
|
26
27
|
|
|
27
28
|
# List all local backups
|
|
@@ -42,6 +43,7 @@ Set up automatic backups via cron (macOS/Linux only).
|
|
|
42
43
|
npx clawon local schedule on
|
|
43
44
|
npx clawon local schedule on --every 6h --max-snapshots 10
|
|
44
45
|
npx clawon local schedule on --include-memory-db
|
|
46
|
+
npx clawon local schedule on --include-sessions
|
|
45
47
|
|
|
46
48
|
# Disable local schedule
|
|
47
49
|
npx clawon local schedule off
|
|
@@ -54,19 +56,42 @@ npx clawon schedule off
|
|
|
54
56
|
npx clawon schedule status
|
|
55
57
|
```
|
|
56
58
|
|
|
59
|
+
### Workspaces
|
|
60
|
+
|
|
61
|
+
Workspaces organize your cloud snapshots by machine or environment — like GitHub repos for your backups. A default workspace is created automatically on login.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# List your workspaces
|
|
65
|
+
npx clawon workspaces list
|
|
66
|
+
|
|
67
|
+
# Create a new workspace
|
|
68
|
+
npx clawon workspaces create "Work Server"
|
|
69
|
+
|
|
70
|
+
# Switch active workspace (affects backup/restore/list)
|
|
71
|
+
npx clawon workspaces switch work-server
|
|
72
|
+
|
|
73
|
+
# Show current workspace info
|
|
74
|
+
npx clawon workspaces info
|
|
75
|
+
```
|
|
76
|
+
|
|
57
77
|
### Cloud Backups (requires account)
|
|
58
78
|
|
|
59
|
-
Cloud backups sync your workspace to Clawon's servers for cross-machine access.
|
|
79
|
+
Cloud backups sync your workspace to Clawon's servers for cross-machine access. Snapshots are scoped to your current workspace.
|
|
60
80
|
|
|
61
81
|
```bash
|
|
62
|
-
# Authenticate
|
|
82
|
+
# Authenticate (env var recommended to avoid shell history)
|
|
83
|
+
export CLAWON_API_KEY=<your-key>
|
|
84
|
+
npx clawon login
|
|
85
|
+
|
|
86
|
+
# Or inline (key may appear in shell history)
|
|
63
87
|
npx clawon login --api-key <your-key>
|
|
64
88
|
|
|
65
89
|
# Create a cloud backup
|
|
66
90
|
npx clawon backup
|
|
67
91
|
npx clawon backup --tag "stable config"
|
|
68
92
|
npx clawon backup --dry-run # Preview without uploading
|
|
69
|
-
npx clawon backup --include-memory-db # Requires Pro
|
|
93
|
+
npx clawon backup --include-memory-db # Requires Hobby or Pro
|
|
94
|
+
npx clawon backup --include-sessions # Requires Hobby or Pro
|
|
70
95
|
|
|
71
96
|
# List cloud backups
|
|
72
97
|
npx clawon list
|
|
@@ -88,8 +113,9 @@ npx clawon activity # Recent events
|
|
|
88
113
|
```bash
|
|
89
114
|
npx clawon discover # Show exactly which files would be backed up
|
|
90
115
|
npx clawon discover --include-memory-db # Include SQLite memory index
|
|
116
|
+
npx clawon discover --include-sessions # Include chat history
|
|
91
117
|
npx clawon schedule status # Show active schedules
|
|
92
|
-
npx clawon status # Connection status and file count
|
|
118
|
+
npx clawon status # Connection status, workspace, and file count
|
|
93
119
|
npx clawon logout # Remove local credentials
|
|
94
120
|
```
|
|
95
121
|
|
|
@@ -122,7 +148,7 @@ These are **always excluded**, even if they match an include pattern:
|
|
|
122
148
|
| `openclaw.json` | May contain credentials |
|
|
123
149
|
| `agents/*/auth.json` | Authentication data |
|
|
124
150
|
| `agents/*/auth-profiles.json` | Auth profiles |
|
|
125
|
-
| `agents/*/sessions/**` | Chat history (large, use
|
|
151
|
+
| `agents/*/sessions/**` | Chat history (large, use `--include-sessions` to include) |
|
|
126
152
|
| `memory/lancedb/**` | Vector database (binary, large) |
|
|
127
153
|
| `memory/*.sqlite` | SQLite databases (use `--include-memory-db` to include) |
|
|
128
154
|
| `*.lock`, `*.wal`, `*.shm` | Database lock files |
|
|
@@ -161,7 +187,7 @@ Each archive contains:
|
|
|
161
187
|
|
|
162
188
|
## Configuration
|
|
163
189
|
|
|
164
|
-
Config is stored at `~/.clawon/config.json` after running `clawon login`. Contains your API key, profile ID, and API URL. Run `clawon logout` to remove it.
|
|
190
|
+
Config is stored at `~/.clawon/config.json` after running `clawon login`. Contains your API key, profile ID, workspace, and API URL. Run `clawon logout` to remove it.
|
|
165
191
|
|
|
166
192
|
## Telemetry
|
|
167
193
|
|
package/dist/index.js
CHANGED
|
@@ -76,10 +76,13 @@ function matchGlob(filePath, pattern) {
|
|
|
76
76
|
let regexPattern = pattern.replace(/\./g, "\\.").replace(/\*\*\//g, "(.*/)?").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*");
|
|
77
77
|
return new RegExp(`^${regexPattern}$`).test(filePath);
|
|
78
78
|
}
|
|
79
|
-
function shouldInclude(relativePath, includeMemoryDb = false) {
|
|
79
|
+
function shouldInclude(relativePath, includeMemoryDb = false, includeSessions = false) {
|
|
80
80
|
if (includeMemoryDb && matchGlob(relativePath, "memory/*.sqlite")) {
|
|
81
81
|
return true;
|
|
82
82
|
}
|
|
83
|
+
if (includeSessions && matchGlob(relativePath, "agents/*/sessions/**")) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
83
86
|
for (const pattern of EXCLUDE_PATTERNS) {
|
|
84
87
|
if (matchGlob(relativePath, pattern)) return false;
|
|
85
88
|
}
|
|
@@ -88,7 +91,7 @@ function shouldInclude(relativePath, includeMemoryDb = false) {
|
|
|
88
91
|
}
|
|
89
92
|
return false;
|
|
90
93
|
}
|
|
91
|
-
function discoverFiles(baseDir, includeMemoryDb = false) {
|
|
94
|
+
function discoverFiles(baseDir, includeMemoryDb = false, includeSessions = false) {
|
|
92
95
|
const files = [];
|
|
93
96
|
function walk(dir, relativePath = "") {
|
|
94
97
|
if (!fs.existsSync(dir)) return;
|
|
@@ -99,7 +102,7 @@ function discoverFiles(baseDir, includeMemoryDb = false) {
|
|
|
99
102
|
if (entry.isDirectory()) {
|
|
100
103
|
walk(fullPath, relPath);
|
|
101
104
|
} else if (entry.isFile()) {
|
|
102
|
-
if (shouldInclude(relPath, includeMemoryDb)) {
|
|
105
|
+
if (shouldInclude(relPath, includeMemoryDb, includeSessions)) {
|
|
103
106
|
const stats = fs.statSync(fullPath);
|
|
104
107
|
files.push({
|
|
105
108
|
path: relPath,
|
|
@@ -112,13 +115,14 @@ function discoverFiles(baseDir, includeMemoryDb = false) {
|
|
|
112
115
|
walk(baseDir);
|
|
113
116
|
return files;
|
|
114
117
|
}
|
|
115
|
-
async function createLocalArchive(files, openclawDir, outputPath, tag, includeMemoryDb, trigger) {
|
|
118
|
+
async function createLocalArchive(files, openclawDir, outputPath, tag, includeMemoryDb, includeSessions, trigger) {
|
|
116
119
|
const meta = {
|
|
117
120
|
version: 2,
|
|
118
121
|
created: (/* @__PURE__ */ new Date()).toISOString(),
|
|
119
122
|
...tag ? { tag } : {},
|
|
120
123
|
file_count: files.length,
|
|
121
124
|
...includeMemoryDb ? { include_memory_db: true } : {},
|
|
125
|
+
...includeSessions ? { include_sessions: true } : {},
|
|
122
126
|
...trigger ? { trigger } : {}
|
|
123
127
|
};
|
|
124
128
|
const metaPath = path.join(openclawDir, "_clawon_meta.json");
|
|
@@ -236,30 +240,46 @@ function assertNotWindows() {
|
|
|
236
240
|
}
|
|
237
241
|
var program = new Command();
|
|
238
242
|
program.name("clawon").description("Backup and restore your OpenClaw workspace").version("0.1.1");
|
|
239
|
-
program.command("login").description("Connect to Clawon with your API key").
|
|
243
|
+
program.command("login").description("Connect to Clawon with your API key").option("--api-key <key>", "Your Clawon API key (or set CLAWON_API_KEY env var)").option("--api-url <url>", "API base URL", "https://clawon.io").action(async (opts) => {
|
|
244
|
+
const apiKey = opts.apiKey || process.env.CLAWON_API_KEY;
|
|
245
|
+
if (!apiKey) {
|
|
246
|
+
console.error("\u2717 API key required. Use --api-key <key> or set CLAWON_API_KEY environment variable.");
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
240
249
|
try {
|
|
241
|
-
const connectJson = await api(opts.apiUrl, "/api/v1/profile/connect", "POST",
|
|
250
|
+
const connectJson = await api(opts.apiUrl, "/api/v1/profile/connect", "POST", apiKey, {
|
|
242
251
|
profileName: "default",
|
|
243
252
|
instanceName: os.hostname(),
|
|
244
253
|
syncIntervalMinutes: 60
|
|
245
254
|
});
|
|
246
255
|
writeConfig({
|
|
247
|
-
apiKey
|
|
256
|
+
apiKey,
|
|
248
257
|
profileId: connectJson.profileId,
|
|
258
|
+
workspaceId: connectJson.workspaceId || void 0,
|
|
259
|
+
workspaceSlug: connectJson.workspaceSlug || void 0,
|
|
249
260
|
apiBaseUrl: opts.apiUrl,
|
|
250
261
|
connectedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
251
262
|
});
|
|
252
263
|
console.log("\u2713 Logged in");
|
|
253
264
|
console.log(` Profile ID: ${connectJson.profileId}`);
|
|
254
|
-
|
|
265
|
+
if (connectJson.workspaceSlug) {
|
|
266
|
+
console.log(` Workspace: ${connectJson.workspaceSlug}`);
|
|
267
|
+
}
|
|
268
|
+
trackCliEvent(connectJson.profileId, "cli_login", {
|
|
269
|
+
workspace_slug: connectJson.workspaceSlug
|
|
270
|
+
});
|
|
255
271
|
} catch (e) {
|
|
256
272
|
console.error(`\u2717 Login failed: ${e.message}`);
|
|
257
273
|
process.exit(1);
|
|
258
274
|
}
|
|
259
275
|
});
|
|
260
|
-
program.command("backup").description("Backup your OpenClaw workspace to the cloud").option("--dry-run", "Show what would be backed up without uploading").option("--tag <label>", "Add a label to this backup").option("--include-memory-db", "Include SQLite memory index").option("--scheduled", "Internal: triggered by cron (suppresses interactive output)").action(async (opts) => {
|
|
276
|
+
program.command("backup").description("Backup your OpenClaw workspace to the cloud").option("--dry-run", "Show what would be backed up without uploading").option("--tag <label>", "Add a label to this backup").option("--include-memory-db", "Include SQLite memory index").option("--include-sessions", "Include chat history (sessions)").option("--scheduled", "Internal: triggered by cron (suppresses interactive output)").action(async (opts) => {
|
|
261
277
|
if (opts.includeMemoryDb) {
|
|
262
|
-
console.error("\u2717 Memory DB cloud backup requires a Pro account. Use `clawon local backup --include-memory-db` for local backups.");
|
|
278
|
+
console.error("\u2717 Memory DB cloud backup requires a Hobby or Pro account. Use `clawon local backup --include-memory-db` for local backups.");
|
|
279
|
+
process.exit(1);
|
|
280
|
+
}
|
|
281
|
+
if (opts.includeSessions) {
|
|
282
|
+
console.error("\u2717 Session backup requires a Hobby or Pro account. Use `clawon local backup --include-sessions` for local backups.");
|
|
263
283
|
process.exit(1);
|
|
264
284
|
}
|
|
265
285
|
const cfg = readConfig();
|
|
@@ -267,6 +287,11 @@ program.command("backup").description("Backup your OpenClaw workspace to the clo
|
|
|
267
287
|
console.error("\u2717 Not logged in. Run: clawon login --api-key <key>");
|
|
268
288
|
process.exit(1);
|
|
269
289
|
}
|
|
290
|
+
if (!cfg.workspaceId) {
|
|
291
|
+
console.error("\u2717 No workspace selected. Run: clawon workspaces switch <slug>");
|
|
292
|
+
console.error(" Or re-login: clawon login --api-key <key>");
|
|
293
|
+
process.exit(1);
|
|
294
|
+
}
|
|
270
295
|
if (!fs.existsSync(OPENCLAW_DIR)) {
|
|
271
296
|
console.error(`\u2717 OpenClaw directory not found: ${OPENCLAW_DIR}`);
|
|
272
297
|
process.exit(1);
|
|
@@ -301,6 +326,7 @@ program.command("backup").description("Backup your OpenClaw workspace to the clo
|
|
|
301
326
|
cfg.apiKey,
|
|
302
327
|
{
|
|
303
328
|
profileId: cfg.profileId,
|
|
329
|
+
workspaceId: cfg.workspaceId,
|
|
304
330
|
files: files.map((f) => ({ path: f.path, size: f.size })),
|
|
305
331
|
...opts.tag ? { tag: opts.tag } : {}
|
|
306
332
|
}
|
|
@@ -335,7 +361,9 @@ program.command("backup").description("Backup your OpenClaw workspace to the clo
|
|
|
335
361
|
file_count: files.length,
|
|
336
362
|
total_bytes: totalSize,
|
|
337
363
|
include_memory_db: !!opts.includeMemoryDb,
|
|
364
|
+
include_sessions: !!opts.includeSessions,
|
|
338
365
|
type: "cloud",
|
|
366
|
+
workspace_slug: cfg.workspaceSlug,
|
|
339
367
|
trigger: opts.scheduled ? "scheduled" : "manual"
|
|
340
368
|
});
|
|
341
369
|
} catch (e) {
|
|
@@ -420,9 +448,10 @@ program.command("list").description("List your backups").option("--limit <n>", "
|
|
|
420
448
|
process.exit(1);
|
|
421
449
|
}
|
|
422
450
|
try {
|
|
451
|
+
const wsParam = cfg.workspaceId ? `&workspaceId=${cfg.workspaceId}` : "";
|
|
423
452
|
const { snapshots } = await api(
|
|
424
453
|
cfg.apiBaseUrl,
|
|
425
|
-
`/api/v1/snapshots/list?profileId=${cfg.profileId}&limit=${opts.limit}`,
|
|
454
|
+
`/api/v1/snapshots/list?profileId=${cfg.profileId}&limit=${opts.limit}${wsParam}`,
|
|
426
455
|
"GET",
|
|
427
456
|
cfg.apiKey
|
|
428
457
|
);
|
|
@@ -430,6 +459,10 @@ program.command("list").description("List your backups").option("--limit <n>", "
|
|
|
430
459
|
console.log("No backups yet. Run: clawon backup");
|
|
431
460
|
return;
|
|
432
461
|
}
|
|
462
|
+
if (cfg.workspaceSlug) {
|
|
463
|
+
console.log(`Workspace: ${cfg.workspaceSlug}
|
|
464
|
+
`);
|
|
465
|
+
}
|
|
433
466
|
console.log("Your backups:\n");
|
|
434
467
|
console.log("ID | Date | Files | Size | Tag");
|
|
435
468
|
console.log("\u2500".repeat(100));
|
|
@@ -546,12 +579,12 @@ program.command("delete [id]").description("Delete a snapshot").option("--oldest
|
|
|
546
579
|
process.exit(1);
|
|
547
580
|
}
|
|
548
581
|
});
|
|
549
|
-
program.command("discover").description("Preview which files would be included in a backup").option("--include-memory-db", "Include SQLite memory index").action(async (opts) => {
|
|
582
|
+
program.command("discover").description("Preview which files would be included in a backup").option("--include-memory-db", "Include SQLite memory index").option("--include-sessions", "Include chat history (sessions)").action(async (opts) => {
|
|
550
583
|
if (!fs.existsSync(OPENCLAW_DIR)) {
|
|
551
584
|
console.error(`\u2717 OpenClaw directory not found: ${OPENCLAW_DIR}`);
|
|
552
585
|
process.exit(1);
|
|
553
586
|
}
|
|
554
|
-
const files = discoverFiles(OPENCLAW_DIR, !!opts.includeMemoryDb);
|
|
587
|
+
const files = discoverFiles(OPENCLAW_DIR, !!opts.includeMemoryDb, !!opts.includeSessions);
|
|
555
588
|
if (files.length === 0) {
|
|
556
589
|
console.log("No files matched the include patterns.");
|
|
557
590
|
return;
|
|
@@ -576,7 +609,7 @@ program.command("discover").description("Preview which files would be included i
|
|
|
576
609
|
Total: ${files.length} files (${(totalSize / 1024).toFixed(1)} KB)`);
|
|
577
610
|
console.log(`Source: ${OPENCLAW_DIR}`);
|
|
578
611
|
const cfg = readConfig();
|
|
579
|
-
trackCliEvent(cfg?.profileId || "anonymous", "cli_discover", { file_count: files.length, include_memory_db: !!opts.includeMemoryDb });
|
|
612
|
+
trackCliEvent(cfg?.profileId || "anonymous", "cli_discover", { file_count: files.length, include_memory_db: !!opts.includeMemoryDb, include_sessions: !!opts.includeSessions });
|
|
580
613
|
});
|
|
581
614
|
var schedule = program.command("schedule").description("Manage scheduled cloud backups");
|
|
582
615
|
schedule.command("on").description("Enable scheduled cloud backups via cron").option("--every <interval>", "Backup interval: 1h, 6h, 12h, 24h", "12h").action(async (opts) => {
|
|
@@ -617,6 +650,7 @@ schedule.command("status").description("Show schedule status").action(async () =
|
|
|
617
650
|
if (localCfg?.intervalHours) console.log(` Interval: every ${localCfg.intervalHours}h`);
|
|
618
651
|
if (localCfg?.maxSnapshots) console.log(` Max snapshots: ${localCfg.maxSnapshots}`);
|
|
619
652
|
if (localCfg?.includeMemoryDb) console.log(` Memory DB: included`);
|
|
653
|
+
if (localCfg?.includeSessions) console.log(` Sessions: included`);
|
|
620
654
|
console.log(` Cron: ${localEntry.trim()}`);
|
|
621
655
|
} else {
|
|
622
656
|
console.log("\u2717 Local schedule: inactive");
|
|
@@ -684,7 +718,7 @@ Total: ${files.length} files`);
|
|
|
684
718
|
});
|
|
685
719
|
var local = program.command("local").description("Local backup and restore (no cloud required)");
|
|
686
720
|
var localSchedule = local.command("schedule").description("Manage scheduled local backups");
|
|
687
|
-
localSchedule.command("on").description("Enable scheduled local backups via cron").option("--every <interval>", "Backup interval: 1h, 6h, 12h, 24h", "12h").option("--max-snapshots <n>", "Keep only the N most recent local backups").option("--include-memory-db", "Include SQLite memory index").action(async (opts) => {
|
|
721
|
+
localSchedule.command("on").description("Enable scheduled local backups via cron").option("--every <interval>", "Backup interval: 1h, 6h, 12h, 24h", "12h").option("--max-snapshots <n>", "Keep only the N most recent local backups").option("--include-memory-db", "Include SQLite memory index").option("--include-sessions", "Include chat history (sessions)").action(async (opts) => {
|
|
688
722
|
assertNotWindows();
|
|
689
723
|
const interval = opts.every;
|
|
690
724
|
if (!VALID_INTERVALS.includes(interval)) {
|
|
@@ -694,7 +728,7 @@ localSchedule.command("on").description("Enable scheduled local backups via cron
|
|
|
694
728
|
const cfg = readConfig();
|
|
695
729
|
const wasEnabled = cfg?.schedule?.local?.enabled;
|
|
696
730
|
const cronExpr = INTERVAL_CRON[interval];
|
|
697
|
-
const args = "local backup --scheduled" + (opts.includeMemoryDb ? " --include-memory-db" : "") + (opts.maxSnapshots ? ` --max-snapshots ${opts.maxSnapshots}` : "");
|
|
731
|
+
const args = "local backup --scheduled" + (opts.includeMemoryDb ? " --include-memory-db" : "") + (opts.includeSessions ? " --include-sessions" : "") + (opts.maxSnapshots ? ` --max-snapshots ${opts.maxSnapshots}` : "");
|
|
698
732
|
const command = resolveCliCommand(args);
|
|
699
733
|
addCronEntry(CRON_MARKER_LOCAL, cronExpr, command);
|
|
700
734
|
const maxSnapshots = opts.maxSnapshots ? parseInt(opts.maxSnapshots, 10) : null;
|
|
@@ -704,7 +738,8 @@ localSchedule.command("on").description("Enable scheduled local backups via cron
|
|
|
704
738
|
enabled: true,
|
|
705
739
|
intervalHours: parseInt(interval),
|
|
706
740
|
...maxSnapshots ? { maxSnapshots } : {},
|
|
707
|
-
...opts.includeMemoryDb ? { includeMemoryDb: true } : {}
|
|
741
|
+
...opts.includeMemoryDb ? { includeMemoryDb: true } : {},
|
|
742
|
+
...opts.includeSessions ? { includeSessions: true } : {}
|
|
708
743
|
}
|
|
709
744
|
}
|
|
710
745
|
});
|
|
@@ -712,8 +747,9 @@ localSchedule.command("on").description("Enable scheduled local backups via cron
|
|
|
712
747
|
console.log(` Interval: every ${interval}`);
|
|
713
748
|
if (maxSnapshots) console.log(` Max snapshots: ${maxSnapshots}`);
|
|
714
749
|
if (opts.includeMemoryDb) console.log(` Memory DB: included`);
|
|
750
|
+
if (opts.includeSessions) console.log(` Sessions: included`);
|
|
715
751
|
console.log(` Log: ${SCHEDULE_LOG}`);
|
|
716
|
-
const firstRunArgs = "local backup" + (opts.includeMemoryDb ? " --include-memory-db" : "") + (opts.maxSnapshots ? ` --max-snapshots ${opts.maxSnapshots}` : "");
|
|
752
|
+
const firstRunArgs = "local backup" + (opts.includeMemoryDb ? " --include-memory-db" : "") + (opts.includeSessions ? " --include-sessions" : "") + (opts.maxSnapshots ? ` --max-snapshots ${opts.maxSnapshots}` : "");
|
|
717
753
|
console.log("\nRunning first backup now...\n");
|
|
718
754
|
try {
|
|
719
755
|
execSync(resolveCliCommand(firstRunArgs), { stdio: "inherit" });
|
|
@@ -724,7 +760,8 @@ localSchedule.command("on").description("Enable scheduled local backups via cron
|
|
|
724
760
|
type: "local",
|
|
725
761
|
interval_hours: parseInt(interval),
|
|
726
762
|
max_snapshots: maxSnapshots,
|
|
727
|
-
include_memory_db: !!opts.includeMemoryDb
|
|
763
|
+
include_memory_db: !!opts.includeMemoryDb,
|
|
764
|
+
include_sessions: !!opts.includeSessions
|
|
728
765
|
});
|
|
729
766
|
});
|
|
730
767
|
localSchedule.command("off").description("Disable scheduled local backups").action(async () => {
|
|
@@ -743,14 +780,14 @@ localSchedule.command("off").description("Disable scheduled local backups").acti
|
|
|
743
780
|
const cfg = readConfig();
|
|
744
781
|
trackCliEvent(cfg?.profileId || "anonymous", "schedule_disabled", { type: "local" });
|
|
745
782
|
});
|
|
746
|
-
local.command("backup").description("Save a local backup of your OpenClaw workspace").option("--tag <label>", "Add a label to this backup").option("--include-memory-db", "Include SQLite memory index").option("--scheduled", "Internal: triggered by cron (suppresses interactive output)").option("--max-snapshots <n>", "Keep only the N most recent local backups").action(async (opts) => {
|
|
783
|
+
local.command("backup").description("Save a local backup of your OpenClaw workspace").option("--tag <label>", "Add a label to this backup").option("--include-memory-db", "Include SQLite memory index").option("--include-sessions", "Include chat history (sessions)").option("--scheduled", "Internal: triggered by cron (suppresses interactive output)").option("--max-snapshots <n>", "Keep only the N most recent local backups").action(async (opts) => {
|
|
747
784
|
if (!fs.existsSync(OPENCLAW_DIR)) {
|
|
748
785
|
console.error(`\u2717 OpenClaw directory not found: ${OPENCLAW_DIR}`);
|
|
749
786
|
process.exit(1);
|
|
750
787
|
}
|
|
751
788
|
try {
|
|
752
789
|
if (!opts.scheduled) console.log("Discovering files...");
|
|
753
|
-
const files = discoverFiles(OPENCLAW_DIR, !!opts.includeMemoryDb);
|
|
790
|
+
const files = discoverFiles(OPENCLAW_DIR, !!opts.includeMemoryDb, !!opts.includeSessions);
|
|
754
791
|
if (files.length === 0) {
|
|
755
792
|
console.error("\u2717 No files found to backup");
|
|
756
793
|
process.exit(1);
|
|
@@ -762,7 +799,7 @@ local.command("backup").description("Save a local backup of your OpenClaw worksp
|
|
|
762
799
|
const filename = `backup-${timestamp}.tar.gz`;
|
|
763
800
|
const filePath = path.join(BACKUPS_DIR, filename);
|
|
764
801
|
if (!opts.scheduled) console.log("Creating archive...");
|
|
765
|
-
await createLocalArchive(files, OPENCLAW_DIR, filePath, opts.tag, opts.includeMemoryDb, opts.scheduled ? "scheduled" : "manual");
|
|
802
|
+
await createLocalArchive(files, OPENCLAW_DIR, filePath, opts.tag, opts.includeMemoryDb, opts.includeSessions, opts.scheduled ? "scheduled" : "manual");
|
|
766
803
|
const archiveSize = fs.statSync(filePath).size;
|
|
767
804
|
if (!opts.scheduled) {
|
|
768
805
|
console.log(`
|
|
@@ -792,6 +829,7 @@ local.command("backup").description("Save a local backup of your OpenClaw worksp
|
|
|
792
829
|
file_count: files.length,
|
|
793
830
|
total_bytes: totalSize,
|
|
794
831
|
include_memory_db: !!opts.includeMemoryDb,
|
|
832
|
+
include_sessions: !!opts.includeSessions,
|
|
795
833
|
type: "local",
|
|
796
834
|
trigger: opts.scheduled ? "scheduled" : "manual",
|
|
797
835
|
rotated_count: rotatedCount
|
|
@@ -897,12 +935,151 @@ local.command("restore").description("Restore from a local backup").option("--fi
|
|
|
897
935
|
process.exit(1);
|
|
898
936
|
}
|
|
899
937
|
});
|
|
938
|
+
var workspaces = program.command("workspaces").description("Manage workspaces");
|
|
939
|
+
workspaces.command("list").description("List your workspaces").action(async () => {
|
|
940
|
+
const cfg = readConfig();
|
|
941
|
+
if (!cfg) {
|
|
942
|
+
console.error("\u2717 Not logged in. Run: clawon login --api-key <key>");
|
|
943
|
+
process.exit(1);
|
|
944
|
+
}
|
|
945
|
+
try {
|
|
946
|
+
const { workspaces: wsList } = await api(
|
|
947
|
+
cfg.apiBaseUrl,
|
|
948
|
+
`/api/v1/workspaces/list?profileId=${cfg.profileId}`,
|
|
949
|
+
"GET",
|
|
950
|
+
cfg.apiKey
|
|
951
|
+
);
|
|
952
|
+
if (!wsList?.length) {
|
|
953
|
+
console.log("No workspaces yet.");
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
console.log("Your workspaces:\n");
|
|
957
|
+
console.log("Name | Slug | Snapshots | Last Backup");
|
|
958
|
+
console.log("\u2500".repeat(80));
|
|
959
|
+
for (const ws of wsList) {
|
|
960
|
+
const lastBackup = ws.lastBackupAt ? new Date(ws.lastBackupAt).toLocaleString() : "Never";
|
|
961
|
+
const current = ws.id === cfg.workspaceId ? " \u2190 current" : "";
|
|
962
|
+
console.log(
|
|
963
|
+
`${ws.name.padEnd(20)} | ${ws.slug.padEnd(20)} | ${String(ws.snapshotCount).padEnd(9)} | ${lastBackup}${current}`
|
|
964
|
+
);
|
|
965
|
+
}
|
|
966
|
+
console.log(`
|
|
967
|
+
Total: ${wsList.length} workspace(s)`);
|
|
968
|
+
trackCliEvent(cfg.profileId, "cli_workspaces_listed", { count: wsList.length });
|
|
969
|
+
} catch (e) {
|
|
970
|
+
console.error(`\u2717 Failed to list workspaces: ${e.message}`);
|
|
971
|
+
process.exit(1);
|
|
972
|
+
}
|
|
973
|
+
});
|
|
974
|
+
workspaces.command("create <name>").description("Create a new workspace").option("--description <desc>", "Workspace description").action(async (name, opts) => {
|
|
975
|
+
const cfg = readConfig();
|
|
976
|
+
if (!cfg) {
|
|
977
|
+
console.error("\u2717 Not logged in. Run: clawon login --api-key <key>");
|
|
978
|
+
process.exit(1);
|
|
979
|
+
}
|
|
980
|
+
try {
|
|
981
|
+
const { workspace } = await api(
|
|
982
|
+
cfg.apiBaseUrl,
|
|
983
|
+
"/api/v1/workspaces/create",
|
|
984
|
+
"POST",
|
|
985
|
+
cfg.apiKey,
|
|
986
|
+
{
|
|
987
|
+
profileId: cfg.profileId,
|
|
988
|
+
name,
|
|
989
|
+
...opts.description ? { description: opts.description } : {}
|
|
990
|
+
}
|
|
991
|
+
);
|
|
992
|
+
console.log(`\u2713 Workspace created`);
|
|
993
|
+
console.log(` Name: ${workspace.name}`);
|
|
994
|
+
console.log(` Slug: ${workspace.slug}`);
|
|
995
|
+
console.log(`
|
|
996
|
+
Switch to it: clawon workspaces switch ${workspace.slug}`);
|
|
997
|
+
trackCliEvent(cfg.profileId, "workspace_created", { name, slug: workspace.slug });
|
|
998
|
+
} catch (e) {
|
|
999
|
+
console.error(`\u2717 Failed to create workspace: ${e.message}`);
|
|
1000
|
+
process.exit(1);
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
workspaces.command("switch <slug>").description("Switch to a different workspace").action(async (slug) => {
|
|
1004
|
+
const cfg = readConfig();
|
|
1005
|
+
if (!cfg) {
|
|
1006
|
+
console.error("\u2717 Not logged in. Run: clawon login --api-key <key>");
|
|
1007
|
+
process.exit(1);
|
|
1008
|
+
}
|
|
1009
|
+
try {
|
|
1010
|
+
const { workspaces: wsList } = await api(
|
|
1011
|
+
cfg.apiBaseUrl,
|
|
1012
|
+
`/api/v1/workspaces/list?profileId=${cfg.profileId}`,
|
|
1013
|
+
"GET",
|
|
1014
|
+
cfg.apiKey
|
|
1015
|
+
);
|
|
1016
|
+
const target = (wsList || []).find((w) => w.slug === slug);
|
|
1017
|
+
if (!target) {
|
|
1018
|
+
console.error(`\u2717 Workspace "${slug}" not found.`);
|
|
1019
|
+
console.error(" Available workspaces:");
|
|
1020
|
+
for (const ws of wsList || []) {
|
|
1021
|
+
console.error(` \u2022 ${ws.slug}`);
|
|
1022
|
+
}
|
|
1023
|
+
process.exit(1);
|
|
1024
|
+
}
|
|
1025
|
+
const previousSlug = cfg.workspaceSlug;
|
|
1026
|
+
updateConfig({ workspaceId: target.id, workspaceSlug: target.slug });
|
|
1027
|
+
console.log(`\u2713 Switched to workspace: ${target.name} (${target.slug})`);
|
|
1028
|
+
trackCliEvent(cfg.profileId, "workspace_switched", {
|
|
1029
|
+
from_slug: previousSlug,
|
|
1030
|
+
to_slug: target.slug
|
|
1031
|
+
});
|
|
1032
|
+
} catch (e) {
|
|
1033
|
+
console.error(`\u2717 Failed to switch workspace: ${e.message}`);
|
|
1034
|
+
process.exit(1);
|
|
1035
|
+
}
|
|
1036
|
+
});
|
|
1037
|
+
workspaces.command("info").description("Show current workspace info").action(async () => {
|
|
1038
|
+
const cfg = readConfig();
|
|
1039
|
+
if (!cfg) {
|
|
1040
|
+
console.error("\u2717 Not logged in. Run: clawon login --api-key <key>");
|
|
1041
|
+
process.exit(1);
|
|
1042
|
+
}
|
|
1043
|
+
if (!cfg.workspaceId) {
|
|
1044
|
+
console.log("No workspace selected. Run: clawon workspaces switch <slug>");
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
try {
|
|
1048
|
+
const { workspaces: wsList } = await api(
|
|
1049
|
+
cfg.apiBaseUrl,
|
|
1050
|
+
`/api/v1/workspaces/list?profileId=${cfg.profileId}`,
|
|
1051
|
+
"GET",
|
|
1052
|
+
cfg.apiKey
|
|
1053
|
+
);
|
|
1054
|
+
const current = (wsList || []).find((w) => w.id === cfg.workspaceId);
|
|
1055
|
+
if (!current) {
|
|
1056
|
+
console.log("Current workspace not found on server. It may have been deleted.");
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
console.log("Current Workspace\n");
|
|
1060
|
+
console.log(` Name: ${current.name}`);
|
|
1061
|
+
console.log(` Slug: ${current.slug}`);
|
|
1062
|
+
console.log(` Snapshots: ${current.snapshotCount}`);
|
|
1063
|
+
if (current.lastBackupAt) {
|
|
1064
|
+
console.log(` Last backup: ${new Date(current.lastBackupAt).toLocaleString()}`);
|
|
1065
|
+
}
|
|
1066
|
+
if (current.description) {
|
|
1067
|
+
console.log(` Description: ${current.description}`);
|
|
1068
|
+
}
|
|
1069
|
+
} catch (e) {
|
|
1070
|
+
console.error(`\u2717 Failed to get workspace info: ${e.message}`);
|
|
1071
|
+
process.exit(1);
|
|
1072
|
+
}
|
|
1073
|
+
});
|
|
900
1074
|
program.command("status").description("Show current status").action(async () => {
|
|
901
1075
|
const cfg = readConfig();
|
|
902
1076
|
console.log("Clawon Status\n");
|
|
903
1077
|
if (cfg) {
|
|
904
1078
|
console.log(`\u2713 Logged in`);
|
|
905
1079
|
console.log(` Profile ID: ${cfg.profileId}`);
|
|
1080
|
+
if (cfg.workspaceSlug) {
|
|
1081
|
+
console.log(` Workspace: ${cfg.workspaceSlug}`);
|
|
1082
|
+
}
|
|
906
1083
|
console.log(` API: ${cfg.apiBaseUrl}`);
|
|
907
1084
|
} else {
|
|
908
1085
|
console.log(`\u2717 Not logged in`);
|