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.
Files changed (3) hide show
  1. package/README.md +32 -6
  2. package/dist/index.js +199 -22
  3. 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 account
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 future `--include-sessions`) |
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").requiredOption("--api-key <key>", "Your Clawon API key").option("--api-url <url>", "API base URL", "https://clawon.io").action(async (opts) => {
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", opts.apiKey, {
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: opts.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
- trackCliEvent(connectJson.profileId, "cli_login");
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`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawon",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "Backup and restore your OpenClaw workspace",
5
5
  "type": "module",
6
6
  "bin": {