libra cloud
Command reference for `libra cloud`
Cloud backup and restore operations (D1/R2).
Synopsis
libra cloud sync [--force] [--batch-size <N>]
libra cloud restore [--repo-id <ID> | --name <NAME>] [--metadata-only]
libra cloud status [--verbose]Description
libra cloud provides backup and restore capabilities using Cloudflare D1 (serverless SQLite) for object indexes and metadata, and Cloudflare R2 (S3-compatible object storage) for git objects. This enables full repository backup to the cloud with incremental sync support.
The sync workflow tracks which objects have been uploaded via an is_synced flag in the local object_index table. Before selecting work, sync reconciles the local .libra/objects store into object_index so older loose or packed objects are not skipped. On each default sync, objects are selected when they are locally unsynced or missing from D1, making repeated syncs efficient while still repairing stale local sync flags after a D1 database change. A --force flag allows re-syncing all indexed local objects and is the recovery path for R2 bucket-side data loss. After objects are synced, repository metadata (references/branches) is serialized to JSON and uploaded to R2, with a content hash check to avoid unnecessary uploads.
Each repository is identified by a UUID (libra.repoid config key) and optionally a human-readable project name (cloud.name config key or directory name). The project name is registered in a D1 repositories table for lookup during restore.
Restore can target a repository by UUID (--repo-id) or project name (--name). It downloads the object index from D1, optionally downloads objects from R2, restores metadata (references), and populates the working directory from HEAD.
Options
Subcommand: sync
Sync local repository to cloud. Uploads objects to R2 and indexes to D1.
| Flag | Description |
|---|---|
--force | Sync all indexed local objects, regardless of local/D1 sync state. Useful for deliberately re-upserting every object or recovering after R2 bucket-side data loss. |
--batch-size <N> | Number of objects to process per batch. Default: 50. Must be at least 1. Smaller batches produce more frequent progress output; larger batches reduce overhead. |
# Incremental repair sync
libra cloud sync
# Force re-sync everything
libra cloud sync --force
# Use smaller batches for verbose progress
libra cloud sync --batch-size 10Subcommand: restore
Restore repository from cloud. Downloads object indexes from D1, objects from R2, and restores metadata and working directory.
| Flag | Description |
|---|---|
--repo-id <ID> | UUID of the repository to restore. Mutually exclusive with --name. One of --repo-id or --name is required. |
--name <NAME> | Human-readable project name to restore. Looked up in the D1 repositories table. Mutually exclusive with --repo-id. |
--metadata-only | Only restore the object index to the local database. Do not download objects from R2 or restore the working directory. Useful for inspecting what a repository contains before doing a full restore. |
# Restore by repository ID
libra cloud restore --repo-id a1b2c3d4-e5f6-7890-abcd-ef1234567890
# Restore by project name
libra cloud restore --name my-project
# Only restore metadata (object index)
libra cloud restore --name my-project --metadata-onlySubcommand: status
Show the current cloud sync status for the repository.
| Flag | Description |
|---|---|
--verbose | Show details of individual unsynced objects (up to 20). |
# Show sync status summary
libra cloud status
# Show detailed status with unsynced object list
libra cloud status --verboseCommon Commands
# Initial sync to cloud
libra cloud sync
# Check sync progress
libra cloud status
# Detailed status showing pending objects
libra cloud status --verbose
# Force re-sync after a failed attempt
libra cloud sync --force
# Restore a repository by name into a fresh directory
libra init
libra cloud restore --name my-project
# Preview what would be restored without downloading objects
libra cloud restore --name my-project --metadata-onlyHuman Output
cloud sync (with objects to sync):
Starting cloud sync...
Found 42 objects to sync.
Progress: 42/42 synced, 0 failed
Sync complete: 42 synced, 0 failed
Syncing metadata...
Metadata synced (3 references).cloud sync (nothing to sync):
Starting cloud sync...
No objects to sync.
Syncing metadata...
Metadata unchanged, skipping upload.cloud restore:
Starting restore for repo: a1b2c3d4-e5f6-7890-abcd-ef1234567890
Found 42 objects in cloud for repo.
Restored 42 object indexes to local database.
Restore complete: 38 downloaded, 4 skipped (already exist), 0 failed
Restoring metadata...
Metadata restored.
Restoring working directory to HEAD (abc1234)
Successfully restored working directory files.cloud restore --metadata-only:
Starting restore for repo: a1b2c3d4-e5f6-7890-abcd-ef1234567890
Found 42 objects in cloud for repo.
Restored 42 object indexes to local database.
Metadata-only restore complete.cloud status:
Cloud Sync Status:
Repo ID: a1b2c3d4-e5f6-7890-abcd-ef1234567890
Total objects: 42
Synced: 40 (95%)
Pending: 2
By object type:
blob: 30/32 synced
tree: 8/8 synced
commit: 2/2 syncedcloud status --verbose:
Cloud Sync Status:
Repo ID: a1b2c3d4-e5f6-7890-abcd-ef1234567890
Total objects: 42
Synced: 40 (95%)
Pending: 2
By object type:
blob: 30/32 synced
tree: 8/8 synced
commit: 2/2 synced
Unsynced objects:
abc123def456... (blob, 1024 bytes)
789012abc345... (blob, 512 bytes)Structured Output
--json and --machine are supported for cloud status and cloud sync.
--json emits a command envelope and --machine emits the same envelope as a
single NDJSON line.
{
"ok": true,
"command": "cloud.status",
"data": {
"repo_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"total_objects": 42,
"synced": 40,
"pending": 2,
"synced_percent": 95,
"by_type": [
{
"object_type": "blob",
"total": 32,
"synced": 30,
"pending": 2
}
]
}
}When --verbose is set, the status payload also includes up to 20
unsynced_objects entries with oid, object_type, and size.
cloud sync --json / --machine emits cloud.sync on successful sync runs:
{
"ok": true,
"command": "cloud.sync",
"data": {
"repo_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"project_name": "my-project",
"total_unsynced": 42,
"synced_count": 42,
"failed_count": 0,
"metadata": {
"status": "synced",
"references": 3
},
"agent_capture": {
"status": "completed",
"sessions_synced": 2,
"sessions_failed": 0,
"checkpoints_synced": 6,
"checkpoints_failed": 0
}
}
}cloud restore --json / --machine emits cloud.restore on successful restore runs:
{
"ok": true,
"command": "cloud.restore",
"data": {
"repo_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"metadata_only": false,
"total_objects": 42,
"indexes_restored": 42,
"object_restore": {
"downloaded": 30,
"skipped": 12,
"failed": 0
},
"metadata": {
"status": "restored"
},
"agent_capture": {
"status": "restored"
}
}
}For cloud restore --metadata-only, the payload keeps metadata_only: true
and omits object_restore.
cloud sync --progress=json emits NDJSON progress events to stderr (no legacy
human progress text on stdout). Event names cover object, metadata, and
agent-capture phases, for example:
{"event":"cloud_sync.start"}
{"event":"cloud_sync.objects.total","total":42}
{"event":"cloud_sync.objects.progress","synced":42,"total":42,"failed":0}
{"event":"cloud_sync.metadata.synced","references":3}
{"event":"cloud_sync.agent_capture.complete","sessions_synced":2,"sessions_failed":0,"checkpoints_synced":6,"checkpoints_failed":0}cloud sync default mode still uses the legacy human progress output.
cloud restore and cloud sync failures continue through Libra's standard CLI
error machinery.
Environment Variables
Cloud operations require the following keys. Libra reads repo-local vault.env.*
entries first, then global vault.env.*, then the matching environment
variables. If all layers are missing for a required key, the command reports the
key and asks you to configure it before retrying.
D1 (required for all operations)
| Key | Description |
|---|---|
LIBRA_D1_ACCOUNT_ID | Cloudflare account ID |
LIBRA_D1_API_TOKEN | Cloudflare API token with D1 access |
LIBRA_D1_DATABASE_ID | D1 database UUID |
R2 (required for sync and full restore)
| Key | Description |
|---|---|
LIBRA_STORAGE_ENDPOINT | S3-compatible endpoint URL |
LIBRA_STORAGE_BUCKET | Bucket name |
LIBRA_STORAGE_ACCESS_KEY | Access key ID |
LIBRA_STORAGE_SECRET_KEY | Secret access key |
LIBRA_STORAGE_REGION | Region (defaults to auto) |
Note: When --metadata-only is used with restore, only D1 variables are required.
Design Rationale
Why D1/R2 specifically?
Libra targets Cloudflare's ecosystem for several reasons. D1 provides serverless SQLite, which aligns with Libra's local SQLite-based architecture: the same query patterns and data model work both locally and in the cloud. R2 provides S3-compatible object storage with no egress fees, which is critical for a VCS where objects are frequently downloaded. The combination provides a fully serverless backup backend with no infrastructure to manage.
Why not generic cloud storage?
Libra already has generic S3-compatible storage support via LIBRA_STORAGE_* environment variables for tiered object caching. The cloud command serves a different purpose: full repository backup including metadata (references, HEAD, config). This requires a structured database (D1) for the object index, not just a blob store. A generic backend would require implementing a metadata layer on top of every storage provider, which adds complexity without clear benefit. Users who need backup to other providers can use the object-level storage tiering instead.
Why a batch-size parameter?
Object sync involves uploading to R2 and then indexing in D1 for each object. For large repositories with thousands of objects, this can take significant time. The --batch-size parameter controls how many objects are processed before a progress report is printed. Smaller batches give more responsive feedback; larger batches reduce per-batch overhead. The default of 50 balances these concerns. A batch size of 1 is allowed for maximum granularity during debugging.
Why --repo-id and --name as mutually exclusive options?
Repository UUIDs are stable and unambiguous but not human-friendly. Project names are human-friendly but can conflict or be renamed. Making them mutually exclusive with one required ensures the user explicitly chooses their lookup strategy. The UUID is stored in local config (libra.repoid) and is authoritative; the name is a convenience alias stored in D1's repositories table.
Why does restore attempt to populate the working directory?
A bare object restore (indexes + objects) leaves the repository in a state where files exist in the object store but the working directory is empty. For most users, the goal of restore is to get back to a working state. Libra automatically checks out HEAD (or the main branch as fallback) after restoring objects. This matches user expectations and avoids an extra manual step. The --metadata-only flag skips this for users who only need the index.
Parameter Comparison: Libra vs Git vs jj
| Operation | Libra | Git | jj |
|---|---|---|---|
| Sync to cloud | cloud sync | N/A (use push to remote) | N/A (use push to remote) |
| Force sync | cloud sync --force | N/A | N/A |
| Batch size | cloud sync --batch-size <N> | N/A | N/A |
| Restore from cloud | cloud restore --name <N> | clone <url> | git clone <url> |
| Restore by ID | cloud restore --repo-id <ID> | N/A | N/A |
| Metadata-only restore | cloud restore --metadata-only | N/A | N/A |
| Sync status | cloud status | N/A | N/A |
| Verbose status | cloud status --verbose | N/A | N/A |
| Backend | Cloudflare D1 + R2 | Git remotes (SSH/HTTPS) | Git remotes (SSH/HTTPS) |
| Incremental sync | Automatic (is_synced flag) | Automatic (pack negotiation) | Automatic (via Git) |
| Object verification | Hash check on restore | Hash check on transfer | Hash check on transfer |
| Metadata backup | Automatic (references JSON) | Included in push/fetch | Included in push/fetch |
Note: Neither Git nor jj have a built-in cloud backup command. They rely on pushing to remote repositories for backup and collaboration. Libra's cloud command fills a different niche: backing up the full repository state (including local branches, config, and object index) to a serverless cloud backend without requiring a Git server.
Error Handling
| Code | Condition |
|---|---|
LBR-REPO-001 | Not a libra repository |
LBR-CLI-002 | Missing required Vault/env credential keys (lists which ones) |
LBR-CLI-002 | Batch size must be at least 1 |
LBR-CLI-002 | Neither --repo-id nor --name provided for restore |
LBR-CLI-003 | Repository with given name not found in D1 |
LBR-CONFLICT-002 | Project name already taken by another repository |
LBR-IO-001 | D1 client initialization failure |
LBR-IO-001 | Failed to create D1 tables |
LBR-IO-001 | Database query failure |
LBR-IO-002 | R2 upload failure |
LBR-IO-002 | R2 download failure |
LBR-IO-002 | Hash mismatch on restored object |
LBR-IO-002 | Failed to save restored object to local storage |
LBR-IO-002 | Metadata sync/restore failure |