Skip to main content

☁️ Cloud Sync

@lighthouse-ai/cloud-sync is the zero-dependency pointer-service client. It maps namespace × network to the latest index-snapshot CID so a fresh device can rebuild without copying CIDs by hand. Install it with npm install @lighthouse-ai/cloud-sync.

import { backupIndex, restoreIndex } from '@lighthouse-ai/cloud-sync'

await backupIndex(memory, { apiUrl, token, namespace: 'default', network: 'mainnet' })
await restoreIndex(memory, { apiUrl, namespace: 'default', network: 'mainnet' })

Both engines speak the same pair - snapshotIndex() / rebuildLocal() - so the helper works unchanged for batched and memwal. (batched.rebuild() remains the no-pointer fallback when storage itself is remote and listable.)

backupIndex(engine, opts)

Snapshots the local index, then upserts the pointer:

const out = await backupIndex(memory, {
apiUrl: 'https://memory-api.lighthouse.storage',
token: '<Clerk session JWT>', // or omit to reuse ~/.memory-sdk-cloud/token
engine: 'batched', // defaults to engine.kind
namespace: 'default',
network: 'mainnet',
})
// { token, cid: 'baf…', pointer: { userId, pointerKey, snapshotCid, engine, namespace, network, memories?, updatedAt } }

Steps: engine.snapshotIndex()PUT /v1/pointers { snapshotCid, engine, namespace, network, memories } with Authorization: Bearer <token>.

restoreIndex(engine, opts)

Fetches the pointer, then merges the snapshot locally:

const out = await restoreIndex(memory, {
apiUrl: 'https://memory-api.lighthouse.storage',
namespace: 'default',
network: 'mainnet',
})
// { token, cid: 'baf…', result: { added, total } }

Steps: GET /v1/pointers/current?namespace=&network=engine.rebuildLocal(snapshotCid). Throws No snapshot saved for namespace "…" on network "…" yet - run backup first. on 404.

Token handling

PrecedenceSource
1Explicit token in the call
2Saved token file (~/.memory-sdk-cloud/token, mode 0600)
3Error: Missing Clerk session JWT - run the cloud-sync login once, or pass token explicitly.

Helpers:

import { loadSavedToken, saveTokenFile, defaultTokenFile, resolveToken } from '@lighthouse-ai/cloud-sync'

await saveTokenFile(jwt) // mkdir -p ~/.memory-sdk-cloud, write mode 0600
await loadSavedToken() // string | null
defaultTokenFile() // ~/.memory-sdk-cloud/token (override with SyncOptions.tokenFile)
await resolveToken({ apiUrl }) // { token } or throws

The cloud-sync login CLI (npx -y @lighthouse-ai/cloud-sync login) opens the dashboard, receives the Clerk JWT via CLI handoff, and saves it with saveTokenFile. The dashboard also exposes Show + copy session token for headless flows - pass that string as token.

SyncEngine / SyncOptions / Pointer

interface SyncEngine {
readonly kind?: string
snapshotIndex(): Promise<{ cid: string; entries: number }>
rebuildLocal(snapshotCid: string): Promise<{ added: number; total: number }>
}

interface SyncOptions {
apiUrl: string
token?: string
engine?: string
namespace?: string // default 'default'
network?: string // default 'mainnet'
tokenFile?: string
}

interface Pointer {
userId: string
pointerKey: string // '<namespace>#<network>'
snapshotCid: string
engine: string
namespace: string
network: string
memories?: number
updatedAt: string
}

Raw API (without the helper)

// backup
const snap = await memory.snapshotIndex()
await fetch(`${API}/v1/pointers`, {
method: 'PUT',
headers: { 'content-type': 'application/json', authorization: `Bearer ${token}` },
body: JSON.stringify({ snapshotCid: snap.cid, engine: 'batched', namespace: 'default', network: 'mainnet' }),
})

// restore
const { pointer } = await fetch(
`${API}/v1/pointers/current?namespace=default&network=mainnet`,
{ headers: { authorization: `Bearer ${token}` } }
).then((r) => r.json())
await memory.rebuildLocal(pointer.snapshotCid)

MVP stores the latest CID per namespace × network (PUT overwrites). Auth is Authorization: Bearer <Clerk session JWT>. See Cross-Device Backup (Step 2's raw-HTTP reference) for the field table and shapes.