openclaw-workspace-sync 2.2.0 → 2.3.0

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 CHANGED
@@ -221,15 +221,28 @@ Add to your `openclaw.json`:
221
221
  "openclaw-workspace-sync": {
222
222
  "enabled": true,
223
223
  "config": {
224
- "provider": "dropbox",
225
- "mode": "mailbox",
226
- "remotePath": "",
227
- "localPath": "/",
228
- "interval": 60,
229
- "timeout": 1800,
230
- "onSessionStart": true,
231
- "onSessionEnd": false,
232
- "exclude": [".git/**", "node_modules/**", "*.log"]
224
+ "sync": {
225
+ "provider": "dropbox",
226
+ "mode": "mailbox",
227
+ "remotePath": "",
228
+ "localPath": "/",
229
+ "interval": 60,
230
+ "timeout": 1800,
231
+ "onSessionStart": true,
232
+ "onSessionEnd": false,
233
+ "exclude": [".git/**", "node_modules/**", "*.log"]
234
+ },
235
+ "backup": {
236
+ "enabled": true,
237
+ "provider": "s3",
238
+ "bucket": "my-backups",
239
+ "prefix": "habibi/",
240
+ "interval": 86400,
241
+ "encrypt": true,
242
+ "passphrase": "${BACKUP_PASSPHRASE}",
243
+ "include": ["workspace", "config", "cron", "memory"],
244
+ "retain": 7
245
+ }
233
246
  }
234
247
  }
235
248
  }
@@ -237,6 +250,8 @@ Add to your `openclaw.json`:
237
250
  }
238
251
  ```
239
252
 
253
+ > **Flat format still works.** Putting `provider`, `mode`, etc. at the config root (without `sync`) is supported for backwards compatibility. The nested `{ sync, backup }` format is recommended for clarity.
254
+
240
255
  ### Config reference
241
256
 
242
257
  | Key | Type | Default | Description |
@@ -563,6 +578,171 @@ These recommendations apply regardless of whether you use this plugin. Cloud syn
563
578
  - **Encryption**: Consider [rclone crypt](https://rclone.org/crypt/) for sensitive data
564
579
  - **App folder**: Use Dropbox app folder access for minimal permissions
565
580
 
581
+ ## Encrypted backups
582
+
583
+ Back up your entire agent system — workspace, config, cron jobs, memory, sessions — as encrypted snapshots to your own cloud storage. Your bucket, your encryption key, zero monthly fees.
584
+
585
+ ### How it works
586
+
587
+ <p align="center">
588
+ <img src="https://raw.githubusercontent.com/ashbrener/openclaw-workspace-sync/main/docs/diagrams/mode-3.svg" alt="backup pipeline diagram" width="700" />
589
+ </p>
590
+
591
+ 1. **Streams directly** — `tar | [openssl enc] | rclone rcat` piped straight to the remote. Zero local temp files, zero extra disk needed. A 10 GB workspace on a 1 GB free volume? No problem.
592
+ 2. Optionally encrypts with AES-256 (client-side, before upload) via `openssl`
593
+ 3. Uploads via rclone to any supported provider (S3, R2, Backblaze B2, Dropbox, etc.)
594
+ 4. Prunes old snapshots based on your retention policy
595
+
596
+ > **Disk-constrained?** Because backups stream directly, you don't need any free disk space for the backup itself. Only the restore downloads to a staging directory.
597
+
598
+ ### Configuration
599
+
600
+ Add `sync` and `backup` blocks to your plugin config. The backup provider can differ from your sync provider — sync to Dropbox for live mirror, backup to S3 for disaster recovery:
601
+
602
+ ```json
603
+ {
604
+ "sync": {
605
+ "provider": "dropbox",
606
+ "mode": "mailbox",
607
+ "remotePath": "",
608
+ "interval": 180
609
+ },
610
+ "backup": {
611
+ "enabled": true,
612
+ "provider": "s3",
613
+ "bucket": "my-backups",
614
+ "prefix": "habibi/",
615
+ "interval": 86400,
616
+ "encrypt": true,
617
+ "passphrase": "${BACKUP_PASSPHRASE}",
618
+ "include": ["workspace", "config", "cron", "memory"],
619
+ "retain": { "daily": 7, "weekly": 4 }
620
+ }
621
+ }
622
+ ```
623
+
624
+ ### Backup config reference
625
+
626
+ | Key | Type | Default | Description |
627
+ |-----|------|---------|-------------|
628
+ | `enabled` | boolean | `false` | Enable scheduled backups |
629
+ | `provider` | string | parent provider | Cloud provider for backup storage |
630
+ | `bucket` | string | — | S3/R2 bucket name |
631
+ | `prefix` | string | `""` | Path prefix within the bucket (e.g., `habibi/`) |
632
+ | `interval` | number | `86400` | Backup interval in seconds (86400 = daily; clamped to min 300) |
633
+ | `encrypt` | boolean | `false` | Encrypt snapshots with AES-256 before upload |
634
+ | `passphrase` | string | — | Encryption passphrase (use `${BACKUP_PASSPHRASE}` env var) |
635
+ | `include` | string[] | see below | What to back up |
636
+ | `retain` | number or object | `7` | Retention: `7` = keep 7 latest, or `{ daily: 7, weekly: 4 }` |
637
+ | `exclude` | string[] | parent excludes | Glob patterns to exclude from workspace backup |
638
+
639
+ ### Include options
640
+
641
+ | Item | What gets backed up |
642
+ |------|---------------------|
643
+ | `workspace` | The workspace directory (agent files, projects, etc.) |
644
+ | `config` | `openclaw.json` |
645
+ | `cron` | Cron job schedules and state |
646
+ | `memory` | Memory files (MEMORY.md, etc.) |
647
+ | `sessions` | Session metadata and store |
648
+ | `credentials` | Auth profile credentials |
649
+ | `skills` | Skill files |
650
+ | `hooks` | Webhook configurations and state (Gmail watch, custom hooks) |
651
+ | `extensions` | Installed plugins/extensions (for reproducible restores) |
652
+ | `env` | Environment variables file (`.env`) |
653
+ | `agents` | Multi-agent state (per-agent sessions, subagent registry) |
654
+ | `pages` | Custom pages served by the gateway |
655
+ | `transcripts` | Full conversation logs (JSONL session transcripts) |
656
+
657
+ Default: `["workspace", "config", "cron", "memory"]`
658
+
659
+ ### CLI commands
660
+
661
+ ```bash
662
+ # Create a backup now
663
+ openclaw workspace-sync backup now
664
+
665
+ # List available snapshots
666
+ openclaw workspace-sync backup list
667
+
668
+ # Restore the latest snapshot
669
+ openclaw workspace-sync backup restore
670
+
671
+ # Restore a specific snapshot
672
+ openclaw workspace-sync backup restore --snapshot backup-20260310T020000Z.tar.gz.enc
673
+
674
+ # Restore to a specific directory (safe — doesn't overwrite live data)
675
+ openclaw workspace-sync backup restore --to /tmp/restore-test
676
+
677
+ # Check backup service status
678
+ openclaw workspace-sync backup status
679
+ ```
680
+
681
+ ### Restore safety
682
+
683
+ By default, `restore` extracts to a staging directory (`~/.openclaw/.backup-restore/`), not directly over your live workspace. This lets you inspect the contents before copying them into place. Use `--to` to control where files land.
684
+
685
+ ### Provider examples
686
+
687
+ **Cloudflare R2 (free tier: 10GB):**
688
+
689
+ ```json
690
+ {
691
+ "backup": {
692
+ "enabled": true,
693
+ "provider": "s3",
694
+ "encrypt": true,
695
+ "passphrase": "${BACKUP_PASSPHRASE}",
696
+ "s3": {
697
+ "endpoint": "https://<account-id>.r2.cloudflarestorage.com",
698
+ "bucket": "openclaw-backups",
699
+ "region": "auto",
700
+ "accessKeyId": "${R2_ACCESS_KEY}",
701
+ "secretAccessKey": "${R2_SECRET_KEY}"
702
+ }
703
+ }
704
+ }
705
+ ```
706
+
707
+ **AWS S3:**
708
+
709
+ ```json
710
+ {
711
+ "backup": {
712
+ "enabled": true,
713
+ "provider": "s3",
714
+ "encrypt": true,
715
+ "passphrase": "${BACKUP_PASSPHRASE}",
716
+ "s3": {
717
+ "bucket": "my-openclaw-backups",
718
+ "region": "us-east-1",
719
+ "accessKeyId": "${AWS_ACCESS_KEY_ID}",
720
+ "secretAccessKey": "${AWS_SECRET_ACCESS_KEY}"
721
+ }
722
+ }
723
+ }
724
+ ```
725
+
726
+ **Backblaze B2:**
727
+
728
+ ```json
729
+ {
730
+ "backup": {
731
+ "enabled": true,
732
+ "provider": "s3",
733
+ "encrypt": true,
734
+ "passphrase": "${BACKUP_PASSPHRASE}",
735
+ "s3": {
736
+ "endpoint": "https://s3.us-west-002.backblazeb2.com",
737
+ "bucket": "openclaw-backups",
738
+ "region": "us-west-002",
739
+ "accessKeyId": "${B2_KEY_ID}",
740
+ "secretAccessKey": "${B2_APP_KEY}"
741
+ }
742
+ }
743
+ }
744
+ ```
745
+
566
746
  ## Development
567
747
 
568
748
  ```bash
package/README.src.md CHANGED
@@ -262,15 +262,28 @@ Add to your `openclaw.json`:
262
262
  "openclaw-workspace-sync": {
263
263
  "enabled": true,
264
264
  "config": {
265
- "provider": "dropbox",
266
- "mode": "mailbox",
267
- "remotePath": "",
268
- "localPath": "/",
269
- "interval": 60,
270
- "timeout": 1800,
271
- "onSessionStart": true,
272
- "onSessionEnd": false,
273
- "exclude": [".git/**", "node_modules/**", "*.log"]
265
+ "sync": {
266
+ "provider": "dropbox",
267
+ "mode": "mailbox",
268
+ "remotePath": "",
269
+ "localPath": "/",
270
+ "interval": 60,
271
+ "timeout": 1800,
272
+ "onSessionStart": true,
273
+ "onSessionEnd": false,
274
+ "exclude": [".git/**", "node_modules/**", "*.log"]
275
+ },
276
+ "backup": {
277
+ "enabled": true,
278
+ "provider": "s3",
279
+ "bucket": "my-backups",
280
+ "prefix": "habibi/",
281
+ "interval": 86400,
282
+ "encrypt": true,
283
+ "passphrase": "${BACKUP_PASSPHRASE}",
284
+ "include": ["workspace", "config", "cron", "memory"],
285
+ "retain": 7
286
+ }
274
287
  }
275
288
  }
276
289
  }
@@ -278,6 +291,8 @@ Add to your `openclaw.json`:
278
291
  }
279
292
  ```
280
293
 
294
+ > **Flat format still works.** Putting `provider`, `mode`, etc. at the config root (without `sync`) is supported for backwards compatibility. The nested `{ sync, backup }` format is recommended for clarity.
295
+
281
296
  ### Config reference
282
297
 
283
298
  | Key | Type | Default | Description |
@@ -604,6 +619,188 @@ These recommendations apply regardless of whether you use this plugin. Cloud syn
604
619
  - **Encryption**: Consider [rclone crypt](https://rclone.org/crypt/) for sensitive data
605
620
  - **App folder**: Use Dropbox app folder access for minimal permissions
606
621
 
622
+ ## Encrypted backups
623
+
624
+ Back up your entire agent system — workspace, config, cron jobs, memory, sessions — as encrypted snapshots to your own cloud storage. Your bucket, your encryption key, zero monthly fees.
625
+
626
+ ### How it works
627
+
628
+ ```mermaid
629
+ flowchart TD
630
+ subgraph GW["Gateway"]
631
+ WS["/workspace"]
632
+ CFG["config / cron / memory"]
633
+ end
634
+ TAR["tar cz"]
635
+ ENC["openssl enc\n(AES-256)"]
636
+ RCAT["rclone rcat"]
637
+ subgraph REMOTE["Your Bucket (S3 / R2 / B2)"]
638
+ SNAP["backup-2026…Z.tar.gz.enc"]
639
+ OLD["older snapshots"]
640
+ end
641
+ WS --> TAR
642
+ CFG --> TAR
643
+ TAR -- "pipe" --> ENC
644
+ ENC -- "pipe" --> RCAT
645
+ RCAT --> SNAP
646
+ SNAP -. "retention prune" .-> OLD
647
+ ```
648
+
649
+ 1. **Streams directly** — `tar | [openssl enc] | rclone rcat` piped straight to the remote. Zero local temp files, zero extra disk needed. A 10 GB workspace on a 1 GB free volume? No problem.
650
+ 2. Optionally encrypts with AES-256 (client-side, before upload) via `openssl`
651
+ 3. Uploads via rclone to any supported provider (S3, R2, Backblaze B2, Dropbox, etc.)
652
+ 4. Prunes old snapshots based on your retention policy
653
+
654
+ > **Disk-constrained?** Because backups stream directly, you don't need any free disk space for the backup itself. Only the restore downloads to a staging directory.
655
+
656
+ ### Configuration
657
+
658
+ Add `sync` and `backup` blocks to your plugin config. The backup provider can differ from your sync provider — sync to Dropbox for live mirror, backup to S3 for disaster recovery:
659
+
660
+ ```json
661
+ {
662
+ "sync": {
663
+ "provider": "dropbox",
664
+ "mode": "mailbox",
665
+ "remotePath": "",
666
+ "interval": 180
667
+ },
668
+ "backup": {
669
+ "enabled": true,
670
+ "provider": "s3",
671
+ "bucket": "my-backups",
672
+ "prefix": "habibi/",
673
+ "interval": 86400,
674
+ "encrypt": true,
675
+ "passphrase": "${BACKUP_PASSPHRASE}",
676
+ "include": ["workspace", "config", "cron", "memory"],
677
+ "retain": { "daily": 7, "weekly": 4 }
678
+ }
679
+ }
680
+ ```
681
+
682
+ ### Backup config reference
683
+
684
+ | Key | Type | Default | Description |
685
+ |-----|------|---------|-------------|
686
+ | `enabled` | boolean | `false` | Enable scheduled backups |
687
+ | `provider` | string | parent provider | Cloud provider for backup storage |
688
+ | `bucket` | string | — | S3/R2 bucket name |
689
+ | `prefix` | string | `""` | Path prefix within the bucket (e.g., `habibi/`) |
690
+ | `interval` | number | `86400` | Backup interval in seconds (86400 = daily; clamped to min 300) |
691
+ | `encrypt` | boolean | `false` | Encrypt snapshots with AES-256 before upload |
692
+ | `passphrase` | string | — | Encryption passphrase (use `${BACKUP_PASSPHRASE}` env var) |
693
+ | `include` | string[] | see below | What to back up |
694
+ | `retain` | number or object | `7` | Retention: `7` = keep 7 latest, or `{ daily: 7, weekly: 4 }` |
695
+ | `exclude` | string[] | parent excludes | Glob patterns to exclude from workspace backup |
696
+
697
+ ### Include options
698
+
699
+ | Item | What gets backed up |
700
+ |------|---------------------|
701
+ | `workspace` | The workspace directory (agent files, projects, etc.) |
702
+ | `config` | `openclaw.json` |
703
+ | `cron` | Cron job schedules and state |
704
+ | `memory` | Memory files (MEMORY.md, etc.) |
705
+ | `sessions` | Session metadata and store |
706
+ | `credentials` | Auth profile credentials |
707
+ | `skills` | Skill files |
708
+ | `hooks` | Webhook configurations and state (Gmail watch, custom hooks) |
709
+ | `extensions` | Installed plugins/extensions (for reproducible restores) |
710
+ | `env` | Environment variables file (`.env`) |
711
+ | `agents` | Multi-agent state (per-agent sessions, subagent registry) |
712
+ | `pages` | Custom pages served by the gateway |
713
+ | `transcripts` | Full conversation logs (JSONL session transcripts) |
714
+
715
+ Default: `["workspace", "config", "cron", "memory"]`
716
+
717
+ ### CLI commands
718
+
719
+ ```bash
720
+ # Create a backup now
721
+ openclaw workspace-sync backup now
722
+
723
+ # List available snapshots
724
+ openclaw workspace-sync backup list
725
+
726
+ # Restore the latest snapshot
727
+ openclaw workspace-sync backup restore
728
+
729
+ # Restore a specific snapshot
730
+ openclaw workspace-sync backup restore --snapshot backup-20260310T020000Z.tar.gz.enc
731
+
732
+ # Restore to a specific directory (safe — doesn't overwrite live data)
733
+ openclaw workspace-sync backup restore --to /tmp/restore-test
734
+
735
+ # Check backup service status
736
+ openclaw workspace-sync backup status
737
+ ```
738
+
739
+ ### Restore safety
740
+
741
+ By default, `restore` extracts to a staging directory (`~/.openclaw/.backup-restore/`), not directly over your live workspace. This lets you inspect the contents before copying them into place. Use `--to` to control where files land.
742
+
743
+ ### Provider examples
744
+
745
+ **Cloudflare R2 (free tier: 10GB):**
746
+
747
+ ```json
748
+ {
749
+ "backup": {
750
+ "enabled": true,
751
+ "provider": "s3",
752
+ "encrypt": true,
753
+ "passphrase": "${BACKUP_PASSPHRASE}",
754
+ "s3": {
755
+ "endpoint": "https://<account-id>.r2.cloudflarestorage.com",
756
+ "bucket": "openclaw-backups",
757
+ "region": "auto",
758
+ "accessKeyId": "${R2_ACCESS_KEY}",
759
+ "secretAccessKey": "${R2_SECRET_KEY}"
760
+ }
761
+ }
762
+ }
763
+ ```
764
+
765
+ **AWS S3:**
766
+
767
+ ```json
768
+ {
769
+ "backup": {
770
+ "enabled": true,
771
+ "provider": "s3",
772
+ "encrypt": true,
773
+ "passphrase": "${BACKUP_PASSPHRASE}",
774
+ "s3": {
775
+ "bucket": "my-openclaw-backups",
776
+ "region": "us-east-1",
777
+ "accessKeyId": "${AWS_ACCESS_KEY_ID}",
778
+ "secretAccessKey": "${AWS_SECRET_ACCESS_KEY}"
779
+ }
780
+ }
781
+ }
782
+ ```
783
+
784
+ **Backblaze B2:**
785
+
786
+ ```json
787
+ {
788
+ "backup": {
789
+ "enabled": true,
790
+ "provider": "s3",
791
+ "encrypt": true,
792
+ "passphrase": "${BACKUP_PASSPHRASE}",
793
+ "s3": {
794
+ "endpoint": "https://s3.us-west-002.backblazeb2.com",
795
+ "bucket": "openclaw-backups",
796
+ "region": "us-west-002",
797
+ "accessKeyId": "${B2_KEY_ID}",
798
+ "secretAccessKey": "${B2_APP_KEY}"
799
+ }
800
+ }
801
+ }
802
+ ```
803
+
607
804
  ## Development
608
805
 
609
806
  ```bash
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Backup manager — encrypted snapshot backups to cloud storage.
3
+ *
4
+ * Streams tar | [openssl enc] | rclone rcat directly to the remote —
5
+ * zero local disk usage. Optionally encrypts with AES-256 via openssl.
6
+ * Prunes old snapshots based on retention policy.
7
+ */
8
+ import type { WorkspaceSyncConfig } from "./types.js";
9
+ type Logger = {
10
+ debug?: (msg: string) => void;
11
+ info: (msg: string) => void;
12
+ warn: (msg: string) => void;
13
+ error: (msg: string) => void;
14
+ };
15
+ export type BackupResult = {
16
+ ok: true;
17
+ snapshotName: string;
18
+ encrypted: boolean;
19
+ sizeBytes: number;
20
+ } | {
21
+ ok: false;
22
+ error: string;
23
+ };
24
+ export type RestoreResult = {
25
+ ok: true;
26
+ snapshotName: string;
27
+ restoredTo: string;
28
+ } | {
29
+ ok: false;
30
+ error: string;
31
+ };
32
+ export declare function runBackup(params: {
33
+ syncConfig: WorkspaceSyncConfig;
34
+ workspaceDir: string;
35
+ stateDir: string;
36
+ logger: Logger;
37
+ }): Promise<BackupResult>;
38
+ export declare function listBackupSnapshots(params: {
39
+ syncConfig: WorkspaceSyncConfig;
40
+ stateDir: string;
41
+ logger: Logger;
42
+ }): Promise<{
43
+ ok: true;
44
+ snapshots: string[];
45
+ } | {
46
+ ok: false;
47
+ error: string;
48
+ }>;
49
+ export declare function runRestore(params: {
50
+ syncConfig: WorkspaceSyncConfig;
51
+ workspaceDir: string;
52
+ stateDir: string;
53
+ snapshotName: string | "latest";
54
+ restoreTo: string;
55
+ logger: Logger;
56
+ }): Promise<RestoreResult>;
57
+ export declare function startBackupManager(syncConfig: WorkspaceSyncConfig, workspaceDir: string, stateDir: string, logger: Logger): void;
58
+ export declare function stopBackupManager(): void;
59
+ export declare function getBackupManagerStatus(): {
60
+ running: boolean;
61
+ lastBackupAt: Date | null;
62
+ lastBackupOk: boolean | null;
63
+ backupCount: number;
64
+ errorCount: number;
65
+ };
66
+ export {};
67
+ //# sourceMappingURL=backup-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backup-manager.d.ts","sourceRoot":"","sources":["../src/backup-manager.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAiD,mBAAmB,EAAyB,MAAM,YAAY,CAAC;AAa5H,KAAK,MAAM,GAAG;IACZ,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B,CAAC;AAiTF,MAAM,MAAM,YAAY,GACpB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACzE;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjC,MAAM,MAAM,aAAa,GACrB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACtD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjC,wBAAsB,SAAS,CAAC,MAAM,EAAE;IACtC,UAAU,EAAE,mBAAmB,CAAC;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,YAAY,CAAC,CAiFxB;AA2BD,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,UAAU,EAAE,mBAAmB,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAe5E;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE;IACvC,UAAU,EAAE,mBAAmB,CAAC;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,GAAG,QAAQ,CAAC;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,aAAa,CAAC,CAoGzB;AAoED,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,mBAAmB,EAC/B,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,IAAI,CAyBN;AAED,wBAAgB,iBAAiB,IAAI,IAAI,CAUxC;AAED,wBAAgB,sBAAsB,IAAI;IACxC,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,IAAI,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB,CAQA"}