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.

The CLI defaults to a local dev stack, so set both URLs when logging in against production:

Env varFlagDefaultProduction value
CM_API_URL--api-urlhttp://127.0.0.1:4000https://memory-api.lighthouse.storage
CM_DASHBOARD_URL--dashboardhttp://127.0.0.1:5173https://memory.lighthouse.storage
CM_TOKEN_FILE--token-file~/.memory-sdk-cloud/token-
export CM_API_URL=https://memory-api.lighthouse.storage
export CM_DASHBOARD_URL=https://memory.lighthouse.storage
npx -y @lighthouse-ai/cloud-sync login

CM_API_URL verifies the token after sign-in; CM_DASHBOARD_URL is the page the browser opens. Other flags: --timeout <sec> (default 300) and --no-open (print the URL instead of opening a browser).

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.