clefbase 2.0.7 → 2.0.9

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 (40) hide show
  1. package/README.md +748 -55
  2. package/dist/__tests__/full-feature-validation.test.d.ts +6 -0
  3. package/dist/__tests__/full-feature-validation.test.d.ts.map +1 -0
  4. package/dist/__tests__/full-feature-validation.test.js +329 -0
  5. package/dist/__tests__/full-feature-validation.test.js.map +1 -0
  6. package/dist/ai.d.ts +294 -9
  7. package/dist/ai.d.ts.map +1 -1
  8. package/dist/ai.js +198 -8
  9. package/dist/ai.js.map +1 -1
  10. package/dist/app.d.ts +7 -0
  11. package/dist/app.d.ts.map +1 -1
  12. package/dist/app.js +18 -3
  13. package/dist/app.js.map +1 -1
  14. package/dist/auth/index.d.ts +28 -3
  15. package/dist/auth/index.d.ts.map +1 -1
  16. package/dist/auth/index.js +71 -9
  17. package/dist/auth/index.js.map +1 -1
  18. package/dist/cli-src/cli/commands/init.js +30 -0
  19. package/dist/cli-src/cli/config.js +3 -0
  20. package/dist/cli.js +32 -1
  21. package/dist/db/index.d.ts +38 -1
  22. package/dist/db/index.d.ts.map +1 -1
  23. package/dist/db/index.js +55 -1
  24. package/dist/db/index.js.map +1 -1
  25. package/dist/index.d.ts +2 -3
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +2 -1
  28. package/dist/index.js.map +1 -1
  29. package/dist/react/index.d.ts +0 -1
  30. package/dist/react/index.d.ts.map +1 -1
  31. package/dist/react/index.js +1 -3
  32. package/dist/react/index.js.map +1 -1
  33. package/dist/storage/index.d.ts +3 -1
  34. package/dist/storage/index.d.ts.map +1 -1
  35. package/dist/storage/index.js +11 -6
  36. package/dist/storage/index.js.map +1 -1
  37. package/dist/types.d.ts +6 -0
  38. package/dist/types.d.ts.map +1 -1
  39. package/dist/types.js.map +1 -1
  40. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # clefbase
2
2
 
3
- Firebase-style SDK and CLI for Clefbase / [Cleforyx](https://cleforyx.com). Database, auth, storage, and hosting in one package.
3
+ Firebase-style SDK and CLI for Clefbase / [Cleforyx](https://cleforyx.com). Database, auth, storage, functions, AI, and hosting in one package.
4
4
 
5
5
  ## Install
6
6
 
@@ -12,7 +12,7 @@ npm install clefbase
12
12
 
13
13
  ## Quick Start
14
14
 
15
- ### 1 — Initialise your project
15
+ ### 1 — Initialize your project
16
16
 
17
17
  ```bash
18
18
  npx clefbase init
@@ -23,15 +23,17 @@ The CLI will ask for your Project ID, API Key, and Admin Secret, let you pick wh
23
23
  ### 2 — Use the SDK
24
24
 
25
25
  ```ts
26
- import { initClefbase, getDatabase, getAuth, getStorage, getHosting } from "clefbase";
26
+ import { initClefbase, getDatabase, getAuth, getStorage, getHosting, getFunctions, getAI } from "clefbase";
27
27
  import config from "./clefbase.json";
28
28
 
29
29
  const app = initClefbase(config);
30
30
 
31
- const db = getDatabase(app);
32
- const auth = getAuth(app);
33
- const storage = getStorage(app);
34
- const hosting = getHosting(app); // requires adminSecret in config
31
+ const db = getDatabase(app);
32
+ const auth = getAuth(app);
33
+ const storage = getStorage(app);
34
+ const hosting = getHosting(app); // requires adminSecret in config
35
+ const functions = getFunctions(app);
36
+ const ai = getAI(app);
35
37
  ```
36
38
 
37
39
  ---
@@ -46,7 +48,7 @@ const post = await db.collection("posts").add({
46
48
  published: true,
47
49
  views: 0,
48
50
  });
49
- // post._id, post._createdAt, post._updatedAt are set by the server
51
+ // post.id, post._createdAt, post._updatedAt are set by the server
50
52
  ```
51
53
 
52
54
  ### Get a document
@@ -107,6 +109,27 @@ await comments.add({ text: "Great post!", author: "Bob" });
107
109
  const all = await comments.getDocs();
108
110
  ```
109
111
 
112
+ ### Batch operations
113
+
114
+ ```ts
115
+ const batch = db.batch();
116
+ batch.set(db.collection("users").doc("user-1"), { name: "Alice" });
117
+ batch.update(db.collection("users").doc("user-2"), { status: "active" });
118
+ batch.delete(db.collection("logs").doc("log-1"));
119
+ await batch.commit();
120
+ ```
121
+
122
+ ### Transactions
123
+
124
+ ```ts
125
+ await db.runTransaction(async (tx) => {
126
+ const balance = await tx.collection("accounts").doc("acc-1").get();
127
+ const newBalance = (balance?.balance ?? 0) - 100;
128
+ tx.update(db.collection("accounts").doc("acc-1"), { balance: newBalance });
129
+ tx.set(db.collection("transactions").doc(), { amount: 100, type: "withdraw" });
130
+ });
131
+ ```
132
+
110
133
  ### Convenience helpers
111
134
 
112
135
  ```ts
@@ -116,10 +139,24 @@ await db.updateDoc("users", "uid-123", { lastSeen: new Date().toISOString() });
116
139
  await db.deleteDoc("users", "uid-123");
117
140
  ```
118
141
 
142
+ ### FieldValue helpers
143
+
144
+ ```ts
145
+ import { FieldValue } from "clefbase";
146
+
147
+ await db.collection("posts").doc("p1").update({
148
+ views: FieldValue.increment(1),
149
+ publishedAt: FieldValue.serverTimestamp(),
150
+ tags: FieldValue.arrayUnion(["new-tag"]),
151
+ });
152
+ ```
153
+
119
154
  ---
120
155
 
121
156
  ## Auth
122
157
 
158
+ ### Email / Password
159
+
123
160
  ```ts
124
161
  // Sign up
125
162
  const { user, token } = await auth.signUp("alice@example.com", "password123", {
@@ -127,76 +164,568 @@ const { user, token } = await auth.signUp("alice@example.com", "password123", {
127
164
  metadata: { role: "member" },
128
165
  });
129
166
 
130
- // Sign in / out
131
- const { user } = await auth.signIn("alice@example.com", "password123");
167
+ // Sign in
168
+ const { user, token } = await auth.signIn("alice@example.com", "password123");
169
+
170
+ // Sign out
132
171
  await auth.signOut();
133
172
 
134
173
  // Current user
135
174
  const me = auth.currentUser; // AuthUser | null
175
+ ```
176
+
177
+ ### Auth state listener
136
178
 
137
- // Listen for auth state changes
179
+ ```ts
138
180
  const unsubscribe = auth.onAuthStateChanged((user) => {
139
181
  if (user) console.log("Signed in as", user.email);
140
182
  else console.log("Signed out");
141
183
  });
184
+
185
+ // Cleanup
142
186
  unsubscribe();
187
+ ```
143
188
 
189
+ ### Profile management
190
+
191
+ ```ts
144
192
  // Update profile
145
- await auth.updateProfile({ displayName: "Bob", metadata: { theme: "dark" } });
193
+ await auth.updateProfile({
194
+ displayName: "Bob",
195
+ metadata: { theme: "dark" }
196
+ });
197
+ ```
146
198
 
147
- // Password management
199
+ ### Password management
200
+
201
+ ```ts
202
+ // Change password
148
203
  await auth.changePassword("oldPass", "newPass");
204
+
205
+ // Send password reset email
149
206
  await auth.sendPasswordResetEmail("alice@example.com");
207
+
208
+ // Confirm reset (call from link in email)
150
209
  await auth.confirmPasswordReset(resetToken, "newPassword");
210
+ ```
151
211
 
152
- // Email verification
212
+ ### Email verification
213
+
214
+ ```ts
215
+ // Send verification email
153
216
  await auth.sendEmailVerification();
217
+
218
+ // Verify email (call from link in email)
154
219
  await auth.verifyEmail("ABC123");
155
220
  ```
156
221
 
222
+ ### OAuth / Social login (Gateway)
223
+
224
+ ```ts
225
+ // 1. Start sign-in with Google/GitHub/etc via gateway
226
+ await auth.signInWithGateway("google");
227
+ // Redirects to auth.cleforyx.com
228
+
229
+ // 2. Handle callback on every app load (before rendering)
230
+ const result = await auth.handleAuthCallback();
231
+ if (result) {
232
+ console.log("Signed in:", result.user.email);
233
+ setAuthToken(app, result.token);
234
+ }
235
+ ```
236
+
157
237
  ---
158
238
 
159
239
  ## Storage
160
240
 
241
+ ### Upload files
242
+
161
243
  ```ts
162
- // Upload (Node.js)
244
+ // Node.js
163
245
  import fs from "fs";
164
246
  const meta = await storage.ref("avatars/user-123.jpg").upload(
165
247
  fs.readFileSync("./photo.jpg"),
166
248
  { contentType: "image/jpeg" }
167
249
  );
168
250
 
169
- // Upload (browser)
170
- const meta = await storage.ref(`uploads/${file.name}`).upload(file);
251
+ // Browser
252
+ const input = document.querySelector("input[type='file']");
253
+ const meta = await storage.ref(`uploads/${input.files[0].name}`).upload(input.files[0]);
254
+
255
+ console.log(meta.id, meta.sizeBytes, meta.md5);
256
+ ```
257
+
258
+ ### Download files
171
259
 
172
- // Download URL
260
+ ```ts
261
+ // Get authenticated download URL
173
262
  const url = await storage.ref("avatars/user-123.jpg").getDownloadURL();
174
263
 
175
- // Metadata
264
+ // Use directly in img tags or fetch
265
+ const response = await fetch(url);
266
+ const blob = await response.blob();
267
+ ```
268
+
269
+ ### File metadata
270
+
271
+ ```ts
176
272
  const meta = await storage.ref("avatars/user-123.jpg").getMetadata();
273
+ console.log(meta.size, meta.mimeType, meta.uploadedAt);
274
+ ```
275
+
276
+ ### Delete files
177
277
 
178
- // Delete
278
+ ```ts
179
279
  await storage.ref("avatars/user-123.jpg").delete();
280
+ ```
180
281
 
181
- // List files
282
+ ### List files
283
+
284
+ ```ts
285
+ // List root files
182
286
  const files = await storage.ref("avatars/").list({ limit: 20 });
183
287
 
184
- // Named bucket
185
- const ref = storage.bucket("user-uploads").ref("doc.pdf");
186
- await ref.upload(buffer, { contentType: "application/pdf" });
288
+ // List with folder filtering
289
+ const folderFiles = await storage.ref("avatars/2024/").list({ offset: 20 });
290
+ ```
291
+
292
+ ### Named buckets
293
+
294
+ ```ts
295
+ const userBucket = storage.bucket("user-uploads");
296
+
297
+ // Upload to bucket
298
+ const file = await userBucket.ref("documents/resume.pdf").upload(buffer, {
299
+ contentType: "application/pdf"
300
+ });
301
+
302
+ // Download from bucket
303
+ const url = await userBucket.ref("documents/resume.pdf").getDownloadURL();
304
+ ```
305
+
306
+ ### Set default bucket
307
+
308
+ ```ts
309
+ storage.setDefaultBucket("media");
310
+ // Now storage.ref() uses "media" instead of "default"
311
+ ```
312
+
313
+ ---
314
+
315
+ ## Functions
316
+
317
+ ### Deploy a function
318
+
319
+ ```ts
320
+ await functions.deploy({
321
+ name: "greetUser",
322
+ runtime: "node",
323
+ trigger: { type: "http" },
324
+ source: `export async function handler(ctx) {
325
+ return { greeting: \`Hello, \${ctx.data.name}!\` };
326
+ }`,
327
+ timeoutMs: 30000,
328
+ });
329
+ ```
330
+
331
+ ### Deploy from file (Node.js/CLI)
332
+
333
+ ```ts
334
+ import { deployFromFile } from "clefbase";
335
+
336
+ await deployFromFile(functions, {
337
+ name: "analyzeImage",
338
+ runtime: "python",
339
+ trigger: { type: "http" },
340
+ filePath: "./functions/analyze_image.py",
341
+ env: { API_KEY: process.env.API_KEY! },
342
+ });
343
+ ```
344
+
345
+ ### Call HTTP-triggered functions
346
+
347
+ ```ts
348
+ // One-shot call
349
+ const { data, durationMs } = await functions.call("greetUser", { name: "Alice" });
350
+
351
+ // Typed callable (recommended)
352
+ const greet = httpsCallable<{ name: string }, { greeting: string }>(functions, "greetUser");
353
+ const result = await greet({ name: "Bob" });
354
+ ```
355
+
356
+ ### Auth-aware function calls
357
+
358
+ ```ts
359
+ import { setAuthToken } from "clefbase";
360
+
361
+ const { token } = await auth.signIn("user@example.com", "password");
362
+ setAuthToken(app, token);
363
+
364
+ // ctx.auth.uid and ctx.auth.email available inside the function
365
+ const { data } = await functions.call("getUserProfile");
366
+ ```
367
+
368
+ ### Scheduled functions (Cron)
369
+
370
+ ```ts
371
+ await functions.deploy({
372
+ name: "dailyReport",
373
+ runtime: "node",
374
+ trigger: {
375
+ type: "cron",
376
+ cron: "0 9 * * *", // Every day at 9 AM UTC
377
+ },
378
+ source: `export async function handler(ctx) {
379
+ // Generate daily report
380
+ return { status: "completed" };
381
+ }`,
382
+ });
383
+ ```
384
+
385
+ ### List and manage functions
386
+
387
+ ```ts
388
+ // List all functions
389
+ const functions = await functions.list();
390
+
391
+ // Get executions / call history
392
+ const executions = await functions.executions("greetUser", 50);
393
+
394
+ // Delete a function
395
+ await functions.delete("greetUser");
396
+ ```
397
+
398
+ ---
399
+
400
+ ## AI
401
+
402
+ ### Text / Code generation
403
+
404
+ ```ts
405
+ import { generateText } from "clefbase";
406
+
407
+ const { content, inputTokens, outputTokens } = await ai.text({
408
+ model: "claude-sonnet-4-5",
409
+ prompt: "Write a bubble-sort in Python",
410
+ systemPrompt: "Return only code, no explanation.",
411
+ maxTokens: 512,
412
+ temperature: 0.3,
413
+ });
414
+
415
+ console.log(content);
416
+ ```
417
+
418
+ ### Vision / Multimodal text generation
419
+
420
+ Send images (from URLs or base64) to text generation models for analysis, object detection, OCR, or image description. Supported by Claude 3+, Gemini 1.5+, and other vision-capable models.
421
+
422
+ ```ts
423
+ // Single image from URL
424
+ const { content } = await ai.text({
425
+ model: "claude-sonnet-4-5",
426
+ prompt: "What objects are in this image? List them.",
427
+ images: [
428
+ { source: "https://example.com/photo.jpg" }
429
+ ],
430
+ });
431
+
432
+ // Multiple images with descriptions
433
+ const { content } = await ai.text({
434
+ model: "gemini-2.5-flash",
435
+ prompt: "Compare these two diagrams and describe the differences",
436
+ images: [
437
+ {
438
+ source: "https://example.com/diagram-1.png",
439
+ description: "Original design"
440
+ },
441
+ {
442
+ source: "https://example.com/diagram-2.png",
443
+ description: "Updated design"
444
+ },
445
+ ],
446
+ });
447
+
448
+ // Base64-encoded image (e.g., from canvas, screenshot, etc.)
449
+ const { content } = await ai.text({
450
+ model: "claude-sonnet-4-5",
451
+ prompt: "Extract and read all text from this screenshot",
452
+ images: [
453
+ {
454
+ source: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEA...",
455
+ mediaType: "image/png"
456
+ }
457
+ ],
458
+ });
459
+
460
+ // Vision with system prompt and history
461
+ const { content } = await ai.text({
462
+ model: "claude-sonnet-4-5",
463
+ prompt: "What color is the car in this image?",
464
+ systemPrompt: "You are a visual expert. Be precise and concise.",
465
+ images: [
466
+ { source: "https://example.com/car.jpg" }
467
+ ],
468
+ history: [
469
+ { role: "user", content: "Analyze this street scene" },
470
+ { role: "assistant", content: "I can see a busy urban street..." },
471
+ ],
472
+ });
473
+ ```
474
+
475
+ ### File attachments for document and code analysis
476
+
477
+ Pass documents, code files, spreadsheets, and other files to text models for analysis, summarization, and extraction. Supports PDFs, Word docs, spreadsheets, code files, JSON, CSV, video/audio transcription, and more.
478
+
479
+ ```ts
480
+ // Summarize a PDF from a URL
481
+ const { content } = await ai.text({
482
+ model: "claude-sonnet-4-5",
483
+ prompt: "Summarize this document in 3 key points",
484
+ files: [
485
+ {
486
+ source: "https://example.com/whitepaper.pdf",
487
+ mediaType: "application/pdf",
488
+ filename: "whitepaper.pdf",
489
+ description: "Technical whitepaper"
490
+ }
491
+ ],
492
+ });
493
+
494
+ // Extract data from a CSV (base64)
495
+ const csvBase64 = "data:text/csv;base64,bmFtZSxhZ2UsY2l0eSpFdmlzLDM1LExvbmRvbg==";
496
+ const { content } = await ai.text({
497
+ model: "gemini-2.5-flash",
498
+ prompt: "Find all people over 30 and their cities",
499
+ files: [
500
+ {
501
+ source: csvBase64,
502
+ mediaType: "text/csv",
503
+ filename: "users.csv",
504
+ description: "User database export"
505
+ }
506
+ ],
507
+ });
508
+
509
+ // Code review and security analysis
510
+ const { content } = await ai.text({
511
+ model: "claude-sonnet-4-5",
512
+ prompt: "Find security vulnerabilities and suggest fixes",
513
+ files: [
514
+ {
515
+ source: "https://example.com/auth-handler.ts",
516
+ mediaType: "text/typescript",
517
+ filename: "auth.ts",
518
+ description: "Authentication handler"
519
+ }
520
+ ],
521
+ });
522
+
523
+ // Compare multiple documents
524
+ const { content } = await ai.text({
525
+ model: "claude-sonnet-4-5",
526
+ prompt: "Are these API schemas compatible? List differences.",
527
+ files: [
528
+ {
529
+ source: "data:application/json;base64,eyJwcm9wc...",
530
+ mediaType: "application/json",
531
+ filename: "v1-schema.json",
532
+ description: "Original API schema"
533
+ },
534
+ {
535
+ source: "data:application/json;base64,eyJwcm9wc...",
536
+ mediaType: "application/json",
537
+ filename: "v2-schema.json",
538
+ description: "New API schema"
539
+ }
540
+ ],
541
+ });
542
+
543
+ // Video/audio transcription and analysis
544
+ const { content } = await ai.text({
545
+ model: "claude-sonnet-4-5",
546
+ prompt: "Transcribe and summarize this podcast episode",
547
+ files: [
548
+ {
549
+ source: "https://example.com/podcast.mp3",
550
+ mediaType: "audio/mpeg",
551
+ filename: "episode-42.mp3",
552
+ description: "Weekly podcast episode"
553
+ }
554
+ ],
555
+ });
556
+ ```
557
+
558
+ ### Local AI models (Ollama)
559
+
560
+ ```ts
561
+ // If Ollama is configured on your server, use local models
562
+ const { content } = await ai.text({
563
+ model: "ollama:mistral",
564
+ prompt: "Explain quantum computing",
565
+ });
566
+ ```
567
+
568
+ ### Image generation
569
+
570
+ **Text-to-Image:**
571
+ ```ts
572
+ const { files } = await ai.image({
573
+ model: "imagen-4.0-generate-001",
574
+ prompt: "A futuristic city at sunset",
575
+ aspectRatio: "16:9",
576
+ numberOfImages: 2,
577
+ outputFolder: "ai-generated", // saved to project storage
578
+ });
579
+
580
+ // Access generated images
581
+ for (const file of files) {
582
+ console.log(file.fullPath); // e.g. "ai-generated/img_123.png"
583
+ const url = await storage.ref(file.fullPath).getDownloadURL();
584
+ }
585
+ ```
586
+
587
+ **Image-to-Image** (style transfer, upscaling, inpainting):
588
+ ```ts
589
+ // Style transfer: apply Van Gogh style to a photo
590
+ const { files } = await ai.image({
591
+ model: "imagen-4.0-generate-001",
592
+ prompt: "Repaint in the style of Van Gogh's Starry Night",
593
+ referenceImage: {
594
+ source: "https://example.com/portrait.jpg",
595
+ strength: 0.6, // 0–1: how much to preserve the reference
596
+ },
597
+ outputFolder: "styled",
598
+ });
599
+
600
+ // Upscaling: enhance and detail an existing image
601
+ const { files: upscaled } = await ai.image({
602
+ model: "imagen-4.0-generate-001",
603
+ prompt: "Upscale with enhanced details, make it sharper and more vibrant",
604
+ referenceImage: {
605
+ source: "data:image/jpeg;base64,/9j/4AAQSkZJRg...", // base64 image
606
+ strength: 0.9, // high similarity to original
607
+ },
608
+ });
609
+
610
+ // Inpainting: modify objects in an image
611
+ const { files: inpainted } = await ai.image({
612
+ model: "imagen-4.0-generate-001",
613
+ prompt: "Replace the sky with a dramatic sunset",
614
+ referenceImage: {
615
+ source: "https://example.com/landscape.jpg",
616
+ strength: 0.7,
617
+ },
618
+ });
619
+ ```
620
+
621
+ ### Video generation (Veo 2)
622
+
623
+ **Text-to-Video:**
624
+ ```ts
625
+ // Async operation — waits for completion (1–5 minutes)
626
+ const { status, files } = await ai.video({
627
+ model: "veo-3.1-generate-preview",
628
+ prompt: "A golden retriever on a sunny beach",
629
+ durationSeconds: 5,
630
+ aspectRatio: "16:9",
631
+ outputFolder: "videos",
632
+ });
633
+
634
+ const videoUrl = await storage.ref(files[0].fullPath).getDownloadURL();
635
+ ```
636
+
637
+ **Image-to-Video** (animate photos, create camera movements):
638
+ ```ts
639
+ // Animate a static photo
640
+ const { files } = await ai.video({
641
+ model: "veo-3.1-generate-preview",
642
+ prompt: "The person starts walking toward the camera with a smile",
643
+ referenceImage: {
644
+ source: "https://example.com/portrait.jpg",
645
+ strength: 0.8, // strong visual consistency
646
+ },
647
+ durationSeconds: 4,
648
+ outputFolder: "animations",
649
+ });
650
+
651
+ // Cinematic camera movement
652
+ const { files: cinematic } = await ai.video({
653
+ model: "veo-3.1-generate-preview",
654
+ prompt: "Smooth dolly-in camera movement, revealing more landscape detail",
655
+ referenceImage: {
656
+ source: "https://example.com/landscape.jpg",
657
+ strength: 0.7,
658
+ },
659
+ durationSeconds: 6,
660
+ aspectRatio: "16:9",
661
+ });
662
+
663
+ // Temporal extension (what happens next?)
664
+ const { files: extended } = await ai.video({
665
+ model: "veo-3.1-generate-preview",
666
+ prompt: "The scene transitions to evening with golden hour lighting",
667
+ referenceImage: {
668
+ source: "data:image/jpeg;base64,/9j/4AAQSkZJRg...",
669
+ strength: 0.6, // more creative freedom
670
+ },
671
+ durationSeconds: 5,
672
+ });
673
+ ```
674
+
675
+ ### Embeddings
676
+
677
+ ```ts
678
+ const { embeddings } = await ai.embedding({
679
+ model: "gemini-embedding-001",
680
+ input: ["apple", "orange", "banana"],
681
+ });
682
+
683
+ // embeddings[0] → vector for "apple"
684
+ // embeddings[1] → vector for "orange"
685
+ // etc.
686
+ ```
687
+
688
+ ### List available models
689
+
690
+ ```ts
691
+ // All models
692
+ const all = await ai.listModels();
693
+
694
+ // Filter by category
695
+ const textModels = await ai.listModels({ category: "text" });
696
+ const imageModels = await ai.listModels({ category: "image" });
697
+
698
+ // Filter by provider
699
+ const googleModels = await ai.listModels({ provider: "google" });
700
+ const anthropicModels = await ai.listModels({ provider: "anthropic" });
701
+ ```
702
+
703
+ ### Usage statistics
704
+
705
+ ```ts
706
+ const stats = await ai.getStats();
707
+ console.log(stats.totalRequests, stats.totalInputTokens, stats.totalMediaGenerated);
708
+
709
+ // Get request log
710
+ const records = await ai.getUsage({ limit: 20 });
711
+ for (const r of records) {
712
+ console.log(r.model, r.status, r.createdAt);
713
+ }
187
714
  ```
188
715
 
189
716
  ---
190
717
 
191
718
  ## Hosting
192
719
 
193
- ### SDK
720
+ ### SDK: Deploy and manage sites
194
721
 
195
722
  ```ts
196
723
  import fs from "fs";
724
+ import { getHosting } from "clefbase";
725
+
197
726
  const hosting = getHosting(app);
198
727
 
199
- // List sites
728
+ // List all sites
200
729
  const sites = await hosting.listSites();
201
730
 
202
731
  // Create a site
@@ -217,51 +746,102 @@ console.log(`Live at: ${result.url}`);
217
746
 
218
747
  // Get active deploy
219
748
  const active = await hosting.site(site.id).getActiveDeploy();
749
+
750
+ // List deploy history
751
+ const deploys = await hosting.site(site.id).listDeploys({ limit: 10 });
220
752
  ```
221
753
 
222
- ### CLI
754
+ ### CLI: Deploy from command line
223
755
 
224
756
  ```bash
225
757
  # First-time setup (interactive)
226
758
  clefbase init
227
759
 
228
- # Build then deploy in one step
760
+ # Build then deploy
229
761
  npm run build && clefbase deploy
230
762
 
231
763
  # Deploy with options
232
764
  clefbase deploy --dir ./build --message "v2 release"
233
- clefbase deploy --site <siteId> # override the linked site
765
+ clefbase deploy --site <siteId> # override linked site
234
766
 
235
- # Manage hosting
236
- clefbase hosting:init # link/create a site
767
+ # Manage sites
768
+ clefbase hosting:init # link or create a site
237
769
  clefbase hosting:status # show current live deploy
238
770
  clefbase hosting:sites # list all sites
771
+ clefbase hosting:list-deploys # show deployment history
239
772
 
240
773
  # Project info
241
- clefbase info # config + connectivity check
774
+ clefbase info # show config and connectivity
775
+ clefbase --version # show SDK version
776
+ ```
777
+
778
+ ### Custom domains
779
+
780
+ ```ts
781
+ // Add custom domain to site
782
+ await hosting.site(siteId).addCustomDomain("app.example.com");
783
+
784
+ // Get custom domains
785
+ const domains = await hosting.site(siteId).getCustomDomains();
786
+
787
+ // Remove custom domain
788
+ await hosting.site(siteId).removeCustomDomain("app.example.com");
242
789
  ```
243
790
 
244
791
  ---
245
792
 
246
- ## CLI Reference
793
+ ## TypeScript
247
794
 
248
- | Command | Description |
249
- |---|---|
250
- | `clefbase init` | Interactive project setup |
251
- | `clefbase deploy` | Deploy your built site |
252
- | `clefbase deploy -d <dir>` | Deploy from a specific directory |
253
- | `clefbase deploy -m <msg>` | Deploy with a release note |
254
- | `clefbase deploy -s <siteId>` | Deploy to a specific site |
255
- | `clefbase hosting:init` | Link or create a hosted site |
256
- | `clefbase hosting:status` | Show current live deploy |
257
- | `clefbase hosting:sites` | List all sites |
258
- | `clefbase info` | Show config and connectivity |
795
+ ### Full type safety
796
+
797
+ ```ts
798
+ import type { ClefbaseDocument } from "clefbase";
799
+
800
+ interface Post extends ClefbaseDocument {
801
+ title: string;
802
+ published: boolean;
803
+ views: number;
804
+ tags: string[];
805
+ }
806
+
807
+ const posts = db.collection<Post>("posts");
808
+ const post = await posts.doc("p1").get();
809
+ // post.views is number ✓, post.author doesn't exist ✗
810
+ ```
811
+
812
+ ### Function types
813
+
814
+ ```ts
815
+ import { httpsCallable } from "clefbase";
816
+
817
+ const add = httpsCallable<
818
+ { a: number; b: number },
819
+ { sum: number }
820
+ >(functions, "add");
821
+
822
+ const { data } = await add({ a: 3, b: 4 });
823
+ console.log(data.sum); // ✓
824
+ // console.log(data.product); // ✗ TypeScript error
825
+ ```
826
+
827
+ ### Auth types
828
+
829
+ ```ts
830
+ import type { AuthUser } from "clefbase";
831
+
832
+ const user: AuthUser | null = auth.currentUser;
833
+ if (user) {
834
+ console.log(user.uid, user.email, user.displayName);
835
+ }
836
+ ```
259
837
 
260
838
  ---
261
839
 
262
- ## clefbase.json
840
+ ## Configuration
263
841
 
264
- Written by `clefbase init`. Never commit this file — it contains your secrets. The CLI adds it to `.gitignore` automatically.
842
+ ### clefbase.json
843
+
844
+ Written by `clefbase init`. Never commit this file — add to `.gitignore` automatically.
265
845
 
266
846
  ```json
267
847
  {
@@ -272,8 +852,10 @@ Written by `clefbase init`. Never commit this file — it contains your secrets.
272
852
  "services": {
273
853
  "database": true,
274
854
  "auth": true,
275
- "storage": false,
276
- "hosting": true
855
+ "storage": true,
856
+ "hosting": true,
857
+ "functions": true,
858
+ "ai": true
277
859
  },
278
860
  "hosting": {
279
861
  "siteId": "355f3976-89dc-...",
@@ -286,18 +868,129 @@ Written by `clefbase init`. Never commit this file — it contains your secrets.
286
868
 
287
869
  ---
288
870
 
289
- ## TypeScript
871
+ ## Error Handling
290
872
 
291
- Full types with generics:
873
+ ### Database errors
292
874
 
293
875
  ```ts
294
- interface Post extends ClefbaseDocument {
295
- title: string;
296
- published: boolean;
297
- views: number;
876
+ import { ClefbaseError } from "clefbase";
877
+
878
+ try {
879
+ await db.collection("users").doc("id").get();
880
+ } catch (err) {
881
+ if (err instanceof ClefbaseError) {
882
+ console.error(err.message, err.code, err.status);
883
+ }
298
884
  }
885
+ ```
299
886
 
300
- const posts = db.collection<Post>("posts");
887
+ ### Functions errors
888
+
889
+ ```ts
890
+ import { FunctionsError } from "clefbase";
891
+
892
+ try {
893
+ await functions.call("myFunction");
894
+ } catch (err) {
895
+ if (err instanceof FunctionsError) {
896
+ console.error(err.message, err.httpStatus);
897
+ }
898
+ }
899
+ ```
900
+
901
+ ### AI errors
902
+
903
+ ```ts
904
+ import { AIError } from "clefbase";
905
+
906
+ try {
907
+ await ai.text({ model: "claude-sonnet-4-5", prompt: "Hi" });
908
+ } catch (err) {
909
+ if (err instanceof AIError) {
910
+ console.error(err.message, err.httpStatus);
911
+ }
912
+ }
913
+ ```
914
+
915
+ ---
916
+
917
+ ## Environment variables
918
+
919
+ Create a `.env` file with:
920
+
921
+ ```bash
922
+ CLEFORYX_SERVER_URL=https://api.cleforyx.com
923
+ CLEFORYX_PROJECT_ID=your_project_id
924
+ CLEFORYX_API_KEY=your_api_key
925
+ CLEFORYX_ADMIN_SECRET=your_admin_secret
926
+ ```
927
+
928
+ Or load into environment and use:
929
+
930
+ ```ts
931
+ const app = initClefbase({
932
+ serverUrl: process.env.CLEFORYX_SERVER_URL!,
933
+ projectId: process.env.CLEFORYX_PROJECT_ID!,
934
+ apiKey: process.env.CLEFORYX_API_KEY!,
935
+ adminSecret: process.env.CLEFORYX_ADMIN_SECRET,
936
+ });
937
+ ```
938
+
939
+ ---
940
+
941
+ ## Advanced topics
942
+
943
+ ### Custom HTTP headers
944
+
945
+ ```ts
946
+ import { HttpClient } from "clefbase";
947
+
948
+ const client = new HttpClient(
949
+ "https://api.cleforyx.com/db",
950
+ { "x-cfx-key": "your-api-key" }
951
+ );
952
+ ```
953
+
954
+ ### Offline-first patterns
955
+
956
+ ```ts
957
+ // Store auth token locally
958
+ localStorage.setItem("cfx_token", token);
959
+
960
+ // On app load
961
+ const saved = localStorage.getItem("cfx_token");
962
+ if (saved) {
963
+ setAuthToken(app, saved);
964
+ const user = auth.currentUser;
965
+ // Use cached state while syncing in background
966
+ }
967
+ ```
968
+
969
+ ### Server-side usage (Node.js)
970
+
971
+ All SDK methods work on Node.js:
972
+
973
+ ```ts
974
+ import { initClefbase, getDatabase } from "clefbase";
975
+
976
+ const app = initClefbase({
977
+ serverUrl: "https://api.cleforyx.com",
978
+ projectId: "my_project",
979
+ apiKey: process.env.CLEFORYX_API_KEY!,
980
+ });
981
+
982
+ const db = getDatabase(app);
983
+ const users = await db.collection("users").getDocs();
984
+ ```
985
+
986
+ ---
987
+
988
+ ## Support
989
+
990
+ - **Docs**: https://cleforyx.com/docs
991
+ - **GitHub**: https://github.com/cleforyx/clefbase
992
+ - **Issues**: https://github.com/cleforyx/clefbase/issues
993
+ - **Community**: https://discord.gg/cleforyx
301
994
  const post = await posts.doc("abc").get(); // Post | null
302
995
  ```
303
996