primitive-admin 1.0.59 → 1.0.61

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 (52) hide show
  1. package/dist/bin/primitive.js +6 -0
  2. package/dist/bin/primitive.js.map +1 -1
  3. package/dist/src/commands/admins.js +22 -8
  4. package/dist/src/commands/admins.js.map +1 -1
  5. package/dist/src/commands/analytics.js +60 -0
  6. package/dist/src/commands/analytics.js.map +1 -1
  7. package/dist/src/commands/blob-buckets.js +37 -2
  8. package/dist/src/commands/blob-buckets.js.map +1 -1
  9. package/dist/src/commands/connections.d.ts +2 -0
  10. package/dist/src/commands/connections.js +95 -0
  11. package/dist/src/commands/connections.js.map +1 -0
  12. package/dist/src/commands/guides.d.ts +4 -29
  13. package/dist/src/commands/guides.js +9 -59
  14. package/dist/src/commands/guides.js.map +1 -1
  15. package/dist/src/commands/init.js +12 -4
  16. package/dist/src/commands/init.js.map +1 -1
  17. package/dist/src/commands/locks.d.ts +8 -0
  18. package/dist/src/commands/locks.js +160 -0
  19. package/dist/src/commands/locks.js.map +1 -0
  20. package/dist/src/commands/sessions.d.ts +2 -0
  21. package/dist/src/commands/sessions.js +63 -0
  22. package/dist/src/commands/sessions.js.map +1 -0
  23. package/dist/src/commands/skill.js +2 -2
  24. package/dist/src/commands/skill.js.map +1 -1
  25. package/dist/src/commands/sync.js +120 -13
  26. package/dist/src/commands/sync.js.map +1 -1
  27. package/dist/src/commands/workflows.js +56 -13
  28. package/dist/src/commands/workflows.js.map +1 -1
  29. package/dist/src/lib/api-client.d.ts +95 -0
  30. package/dist/src/lib/api-client.js +137 -15
  31. package/dist/src/lib/api-client.js.map +1 -1
  32. package/dist/src/lib/channel.d.ts +30 -0
  33. package/dist/src/lib/channel.js +68 -0
  34. package/dist/src/lib/channel.js.map +1 -0
  35. package/dist/src/lib/generated-allowlist.js +11 -0
  36. package/dist/src/lib/generated-allowlist.js.map +1 -1
  37. package/dist/src/lib/skill-installer.d.ts +4 -2
  38. package/dist/src/lib/skill-installer.js +139 -11
  39. package/dist/src/lib/skill-installer.js.map +1 -1
  40. package/dist/src/lib/template.d.ts +16 -1
  41. package/dist/src/lib/template.js +37 -6
  42. package/dist/src/lib/template.js.map +1 -1
  43. package/dist/src/lib/workflow-fragments.d.ts +23 -0
  44. package/dist/src/lib/workflow-fragments.js +229 -8
  45. package/dist/src/lib/workflow-fragments.js.map +1 -1
  46. package/dist/src/lib/workflow-payload.d.ts +1 -1
  47. package/dist/src/lib/workflow-payload.js +17 -0
  48. package/dist/src/lib/workflow-payload.js.map +1 -1
  49. package/dist/src/lib/workflow-toml-validator.d.ts +57 -1
  50. package/dist/src/lib/workflow-toml-validator.js +104 -1
  51. package/dist/src/lib/workflow-toml-validator.js.map +1 -1
  52. package/package.json +1 -1
@@ -1,3 +1,20 @@
1
+ /**
2
+ * Metadata for a single bucket blob, as returned by the metadata endpoint
3
+ * `GET blob-buckets/:bucketId/blobs/:blobId/metadata`. Mirrors the published
4
+ * JS client's `BlobInfo` shape.
5
+ */
6
+ export interface BlobInfo {
7
+ blobId: string;
8
+ bucketId: string;
9
+ filename: string | null;
10
+ contentType: string | null;
11
+ numBytes: number;
12
+ sha256: string | null;
13
+ tags: string[];
14
+ createdBy: string | null;
15
+ uploaded?: string | null;
16
+ etag?: string | null;
17
+ }
1
18
  export declare class ApiError extends Error {
2
19
  statusCode: number;
3
20
  code?: string;
@@ -262,6 +279,22 @@ interface AnalyticsEventsGroupedResponse {
262
279
  rows: AnalyticsGroupedRow[];
263
280
  _timing: AnalyticsTiming;
264
281
  }
282
+ interface AnalyticsErrorGroupRow {
283
+ fingerprint: string;
284
+ normalized_title: string;
285
+ source: string;
286
+ status_class: string;
287
+ total: number;
288
+ daily: Array<{
289
+ day: string;
290
+ count: number;
291
+ }>;
292
+ }
293
+ interface AnalyticsErrorsGroupsResponse {
294
+ window_days: number;
295
+ rows: AnalyticsErrorGroupRow[];
296
+ _timing: AnalyticsTiming;
297
+ }
265
298
  interface AnalyticsIntegrationMetric {
266
299
  integrationKey: string;
267
300
  invocations: number;
@@ -379,6 +412,9 @@ export declare class ApiClient {
379
412
  cursor?: string;
380
413
  }): Promise<{
381
414
  items: any[];
415
+ nextCursor?: string;
416
+ hasMore?: boolean;
417
+ /** @deprecated Alias of `nextCursor` (#1316). */
382
418
  cursor?: string;
383
419
  }>;
384
420
  createInvitation(appId: string, data: {
@@ -405,9 +441,11 @@ export declare class ApiClient {
405
441
  type?: string;
406
442
  email?: string;
407
443
  limit?: number;
444
+ cursor?: string;
408
445
  }): Promise<{
409
446
  grants: any[];
410
447
  nextCursor?: string | null;
448
+ hasMore?: boolean;
411
449
  }>;
412
450
  revokeDeferredGrant(appId: string, deferredId: string, type: string): Promise<any>;
413
451
  listIntegrations(appId: string, params?: {
@@ -482,7 +520,38 @@ export declare class ApiClient {
482
520
  listWebhookEvents(appId: string, webhookId: string, params?: {
483
521
  limit?: number;
484
522
  }): Promise<any>;
523
+ /**
524
+ * List the live connections the server believes exist for exactly one of a
525
+ * document, user, or database. Read-only inspection over admin-api. Rows are
526
+ * per-connection-×-subscription bindings, so `connectionId` is not unique
527
+ * across rows. `params` on a database-subscription row is caller-defined
528
+ * bound CEL input — typed `unknown`.
529
+ */
530
+ listConnections(appId: string, selector: {
531
+ documentId?: string;
532
+ userId?: string;
533
+ databaseId?: string;
534
+ }, params?: {
535
+ limit?: number;
536
+ cursor?: string;
537
+ }): Promise<{
538
+ items: unknown[];
539
+ nextCursor?: string | null;
540
+ }>;
541
+ listSessions(appId: string, userId: string, params?: {
542
+ limit?: number;
543
+ cursor?: string;
544
+ }): Promise<{
545
+ items: unknown[];
546
+ nextCursor?: string | null;
547
+ }>;
485
548
  testWebhook(appId: string, webhookId: string, payload?: any): Promise<any>;
549
+ listLocks(appId: string): Promise<{
550
+ locks: any[];
551
+ }>;
552
+ getLockStatus(appId: string, key: string): Promise<any>;
553
+ acquireLock(appId: string, key: string, ttlMs: number): Promise<any>;
554
+ releaseLock(appId: string, key: string, handleId: string): Promise<any>;
486
555
  listCronTriggers(appId: string): Promise<{
487
556
  items: any[];
488
557
  }>;
@@ -710,6 +779,8 @@ export declare class ApiClient {
710
779
  forward?: boolean;
711
780
  }): Promise<{
712
781
  items: any[];
782
+ nextCursor?: string | null;
783
+ /** @deprecated Alias of `nextCursor` (#1316). */
713
784
  cursor?: string | null;
714
785
  }>;
715
786
  getWorkflowRunStatus(appId: string, workflowId: string, runId: string): Promise<any>;
@@ -785,6 +856,11 @@ export declare class ApiClient {
785
856
  windowDays?: number;
786
857
  [key: string]: any;
787
858
  }): Promise<AnalyticsEventsGroupedResponse>;
859
+ getAnalyticsErrorsGroups(appId: string, params?: {
860
+ windowDays?: number;
861
+ limit?: number;
862
+ [key: string]: any;
863
+ }): Promise<AnalyticsErrorsGroupsResponse>;
788
864
  getAnalyticsTopWorkflows(appId: string, params?: {
789
865
  windowDays?: number;
790
866
  limit?: number;
@@ -1050,6 +1126,8 @@ export declare class ApiClient {
1050
1126
  timestamps?: Record<string, any> | null;
1051
1127
  schema?: string | null;
1052
1128
  metadataManifest?: any;
1129
+ }, options?: {
1130
+ dryRun?: boolean;
1053
1131
  }): Promise<any>;
1054
1132
  updateDatabaseTypeConfig(appId: string, databaseType: string, data: {
1055
1133
  ruleSetId?: string | null;
@@ -1062,6 +1140,11 @@ export declare class ApiClient {
1062
1140
  pendingOpDeletes?: string[];
1063
1141
  finalOpNames?: string[];
1064
1142
  metadataManifest?: any;
1143
+ pendingOpUpdates?: Array<{
1144
+ name: string;
1145
+ access?: string | null;
1146
+ params?: Record<string, any> | null;
1147
+ }>;
1065
1148
  }, expectedModifiedAt?: string, options?: {
1066
1149
  dryRun?: boolean;
1067
1150
  acceptWarnings?: boolean;
@@ -1089,10 +1172,12 @@ export declare class ApiClient {
1089
1172
  dryRun?: boolean;
1090
1173
  schemaOverride?: string | null;
1091
1174
  defaultAccess?: string | null;
1175
+ metadataManifestOverride?: Record<string, any> | null;
1092
1176
  }): Promise<any>;
1093
1177
  updateDatabaseTypeOperation(appId: string, databaseType: string, name: string, data: Record<string, any>, expectedModifiedAt?: string, options?: {
1094
1178
  dryRun?: boolean;
1095
1179
  schemaOverride?: string | null;
1180
+ metadataManifestOverride?: Record<string, any> | null;
1096
1181
  }): Promise<any>;
1097
1182
  deleteDatabaseTypeOperation(appId: string, databaseType: string, name: string): Promise<any>;
1098
1183
  listDatabaseTypeSubscriptions(appId: string, databaseType: string): Promise<any[]>;
@@ -1117,6 +1202,9 @@ export declare class ApiClient {
1117
1202
  cursor?: string;
1118
1203
  }): Promise<{
1119
1204
  items: any[];
1205
+ nextCursor?: string;
1206
+ hasMore?: boolean;
1207
+ /** @deprecated Alias of `nextCursor` (#1316). */
1120
1208
  cursor?: string;
1121
1209
  }>;
1122
1210
  createGroup(appId: string, data: {
@@ -1135,6 +1223,9 @@ export declare class ApiClient {
1135
1223
  include?: "profiles";
1136
1224
  }): Promise<{
1137
1225
  items: any[];
1226
+ nextCursor?: string;
1227
+ hasMore?: boolean;
1228
+ /** @deprecated Alias of `nextCursor` (#1316). */
1138
1229
  cursor?: string;
1139
1230
  }>;
1140
1231
  addGroupMember(appId: string, groupType: string, groupId: string, data: {
@@ -1345,6 +1436,9 @@ export declare class ApiClient {
1345
1436
  limit?: number;
1346
1437
  }): Promise<{
1347
1438
  items: any[];
1439
+ nextCursor?: string;
1440
+ hasMore?: boolean;
1441
+ /** @deprecated Alias of `nextCursor` (#1316). */
1348
1442
  cursor?: string;
1349
1443
  }>;
1350
1444
  uploadBucketBlob(appId: string, bucketIdOrKey: string, data: Buffer, meta: {
@@ -1356,6 +1450,7 @@ export declare class ApiClient {
1356
1450
  deleteBucketBlob(appId: string, bucketIdOrKey: string, blobId: string): Promise<any>;
1357
1451
  deleteBucketBlobs(appId: string, bucketIdOrKey: string, blobIds: string[]): Promise<any>;
1358
1452
  getBucketBlobSignedUrl(appId: string, bucketIdOrKey: string, blobId: string, expiresInSeconds?: number): Promise<any>;
1453
+ getBucketBlobMetadata(appId: string, bucketIdOrKey: string, blobId: string): Promise<BlobInfo>;
1359
1454
  }
1360
1455
  export declare const apiClient: ApiClient;
1361
1456
  export {};
@@ -313,7 +313,14 @@ export class ApiClient {
313
313
  if (cursor)
314
314
  qs.set("cursor", cursor);
315
315
  const resp = await this.get(`/admin/api/admins/me/apps?${qs.toString()}`);
316
- return { items: Array.isArray(resp.apps) ? resp.apps : [], nextCursor: resp.nextCursor };
316
+ // The server now emits `items` with `apps` as a deprecation-window
317
+ // alias (#1316). Read `items` first so listings keep working once the
318
+ // alias is removed; fall back to the legacy `apps` key meanwhile.
319
+ return {
320
+ items: resp.items ??
321
+ (Array.isArray(resp.apps) ? resp.apps : []),
322
+ nextCursor: resp.nextCursor ?? resp.cursor,
323
+ };
317
324
  });
318
325
  return { apps };
319
326
  }
@@ -403,7 +410,16 @@ export class ApiClient {
403
410
  // INVITATIONS
404
411
  // ============================================
405
412
  async listInvitations(appId, params) {
406
- return this.get(`/app/${appId}/api/invitations`, params);
413
+ const result = await this.get(`/app/${appId}/api/invitations`, params);
414
+ // #1316: prefer `nextCursor`; fall back to the deprecated `cursor` alias
415
+ // so the CLI keeps paginating against pre-#1316 servers.
416
+ const nextCursor = result?.nextCursor ?? result?.cursor;
417
+ return {
418
+ items: result?.items ?? [],
419
+ nextCursor,
420
+ hasMore: result?.hasMore ?? nextCursor != null,
421
+ cursor: nextCursor,
422
+ };
407
423
  }
408
424
  async createInvitation(appId, data) {
409
425
  return this.post(`/app/${appId}/api/invitations`, data);
@@ -433,8 +449,9 @@ export class ApiClient {
433
449
  async listDeferredGrants(appId, params) {
434
450
  const result = await this.get(`/app/${appId}/api/deferred-grants`, params);
435
451
  return {
436
- grants: result?.grants ?? [],
452
+ grants: result?.grants ?? result?.items ?? [],
437
453
  nextCursor: result?.nextCursor ?? null,
454
+ hasMore: result?.hasMore,
438
455
  };
439
456
  }
440
457
  async revokeDeferredGrant(appId, deferredId, type) {
@@ -576,10 +593,59 @@ export class ApiClient {
576
593
  const result = await this.get(`/admin/api/apps/${appId}/webhooks/${webhookId}/events`, params);
577
594
  return result;
578
595
  }
596
+ // ============================================
597
+ // CONNECTIONS INSPECTION (#1968)
598
+ // ============================================
599
+ /**
600
+ * List the live connections the server believes exist for exactly one of a
601
+ * document, user, or database. Read-only inspection over admin-api. Rows are
602
+ * per-connection-×-subscription bindings, so `connectionId` is not unique
603
+ * across rows. `params` on a database-subscription row is caller-defined
604
+ * bound CEL input — typed `unknown`.
605
+ */
606
+ async listConnections(appId, selector, params) {
607
+ const result = await this.get(`/admin/api/apps/${appId}/connections`, {
608
+ ...selector,
609
+ ...params,
610
+ });
611
+ return {
612
+ items: result?.items ?? [],
613
+ nextCursor: result?.nextCursor ?? null,
614
+ };
615
+ }
616
+ async listSessions(appId, userId, params) {
617
+ const result = await this.get(`/admin/api/apps/${appId}/sessions`, {
618
+ userId,
619
+ ...params,
620
+ });
621
+ return {
622
+ items: result?.items ?? [],
623
+ nextCursor: result?.nextCursor ?? null,
624
+ };
625
+ }
579
626
  async testWebhook(appId, webhookId, payload) {
580
627
  return this.post(`/admin/api/apps/${appId}/webhooks/${webhookId}/test`, payload || {});
581
628
  }
582
629
  // ============================================
630
+ // NAMED LOCKS (#1518)
631
+ // ============================================
632
+ async listLocks(appId) {
633
+ const result = await this.get(`/app/${appId}/api/locks`);
634
+ return { locks: result?.locks ?? [] };
635
+ }
636
+ async getLockStatus(appId, key) {
637
+ return this.post(`/app/${appId}/api/locks/status`, { key });
638
+ }
639
+ async acquireLock(appId, key, ttlMs) {
640
+ return this.post(`/app/${appId}/api/locks/acquire`, { key, ttlMs });
641
+ }
642
+ async releaseLock(appId, key, handleId) {
643
+ return this.post(`/app/${appId}/api/locks/release`, {
644
+ key,
645
+ handle: { handleId },
646
+ });
647
+ }
648
+ // ============================================
583
649
  // CRON TRIGGERS
584
650
  // ============================================
585
651
  async listCronTriggers(appId) {
@@ -933,9 +999,12 @@ export class ApiClient {
933
999
  }
934
1000
  async listWorkflowRuns(appId, workflowId, params) {
935
1001
  const result = await this.get(`/admin/api/apps/${appId}/workflows/${workflowId}/runs`, params);
1002
+ // #1316: prefer `nextCursor`; fall back to the deprecated `cursor` alias.
1003
+ const nextCursor = result?.nextCursor ?? result?.cursor ?? null;
936
1004
  return {
937
1005
  items: result?.items ?? [],
938
- cursor: result?.cursor ?? null,
1006
+ nextCursor,
1007
+ cursor: nextCursor,
939
1008
  };
940
1009
  }
941
1010
  async getWorkflowRunStatus(appId, workflowId, runId) {
@@ -1021,6 +1090,9 @@ export class ApiClient {
1021
1090
  async getAnalyticsEventsGrouped(appId, params) {
1022
1091
  return this.get(`/app/${appId}/api/analytics/events/grouped`, params);
1023
1092
  }
1093
+ async getAnalyticsErrorsGroups(appId, params) {
1094
+ return this.get(`/app/${appId}/api/analytics/errors/groups`, params);
1095
+ }
1024
1096
  async getAnalyticsTopWorkflows(appId, params) {
1025
1097
  return this.get(`/app/${appId}/api/analytics/workflows/top`, params);
1026
1098
  }
@@ -1032,9 +1104,12 @@ export class ApiClient {
1032
1104
  // ============================================
1033
1105
  async listAllAdmins(params) {
1034
1106
  const result = await this.get("/admin/api/admins", params);
1107
+ // #1316: prefer `nextCursor`; fall back to the deprecated `cursor` alias.
1108
+ const nextCursor = result?.nextCursor ?? result?.cursor ?? null;
1035
1109
  return {
1036
- admins: result?.admins ?? [],
1037
- cursor: result?.cursor ?? null,
1110
+ admins: result?.admins ?? result?.items ?? [],
1111
+ nextCursor,
1112
+ cursor: nextCursor,
1038
1113
  };
1039
1114
  }
1040
1115
  async searchAdminByEmail(email) {
@@ -1338,8 +1413,14 @@ export class ApiClient {
1338
1413
  async getDatabaseTypeConfig(appId, databaseType) {
1339
1414
  return this.get(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}`);
1340
1415
  }
1341
- async createDatabaseTypeConfig(appId, data) {
1342
- return this.post(`/app/${appId}/api/databases/types`, data);
1416
+ async createDatabaseTypeConfig(appId, data,
1417
+ // Issue #1336 (codex round-1 P2): `dryRun` runs every create-time
1418
+ // validation (inline CEL rule lint, manifest shape, schema TOML parse)
1419
+ // WITHOUT persisting, so a fresh-type `sync push --dry-run` surfaces a
1420
+ // malformed `defaultAccess`/manifest the same way the real POST would.
1421
+ options) {
1422
+ const query = options?.dryRun ? "?dryRun=true" : "";
1423
+ return this.post(`/app/${appId}/api/databases/types${query}`, data);
1343
1424
  }
1344
1425
  async updateDatabaseTypeConfig(appId, databaseType, data, expectedModifiedAt, options) {
1345
1426
  const body = expectedModifiedAt ? { ...data, expectedModifiedAt } : data;
@@ -1394,16 +1475,24 @@ export class ApiClient {
1394
1475
  if (options?.dryRun && options.defaultAccess !== undefined) {
1395
1476
  body = { ...body, defaultAccess: options.defaultAccess };
1396
1477
  }
1478
+ if (options?.dryRun && options.metadataManifestOverride !== undefined) {
1479
+ body = { ...body, metadataManifestOverride: options.metadataManifestOverride };
1480
+ }
1397
1481
  return this.post(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/operations${query}`, body);
1398
1482
  }
1399
1483
  async updateDatabaseTypeOperation(appId, databaseType, name, data, expectedModifiedAt,
1400
1484
  // Issue #813 (1A): dry-run runs the op-edit gate without persisting.
1401
1485
  // `schemaOverride` gates against the schema the same push is landing.
1486
+ // Issue #1336: `metadataManifestOverride` gates the op-access lint against
1487
+ // the manifest the same push is landing (see `createDatabaseTypeOperation`).
1402
1488
  options) {
1403
1489
  let body = expectedModifiedAt ? { ...data, expectedModifiedAt } : { ...data };
1404
1490
  if (options?.dryRun && options.schemaOverride !== undefined) {
1405
1491
  body = { ...body, schemaOverride: options.schemaOverride };
1406
1492
  }
1493
+ if (options?.dryRun && options.metadataManifestOverride !== undefined) {
1494
+ body = { ...body, metadataManifestOverride: options.metadataManifestOverride };
1495
+ }
1407
1496
  const query = options?.dryRun ? "?dryRun=true" : "";
1408
1497
  return this.patch(`/app/${appId}/api/databases/types/${encodeURIComponent(databaseType)}/operations/${encodeURIComponent(name)}${query}`, body);
1409
1498
  }
@@ -1447,7 +1536,16 @@ export class ApiClient {
1447
1536
  // GROUPS
1448
1537
  // ============================================
1449
1538
  async listGroups(appId, params) {
1450
- return this.get(`/app/${appId}/api/groups`, params);
1539
+ const result = await this.get(`/app/${appId}/api/groups`, params);
1540
+ // #1316: prefer `nextCursor`; fall back to the deprecated `cursor` alias
1541
+ // so the CLI keeps paginating against pre-#1316 servers.
1542
+ const nextCursor = result?.nextCursor ?? result?.cursor;
1543
+ return {
1544
+ items: result?.items ?? [],
1545
+ nextCursor,
1546
+ hasMore: result?.hasMore ?? nextCursor != null,
1547
+ cursor: nextCursor,
1548
+ };
1451
1549
  }
1452
1550
  async createGroup(appId, data) {
1453
1551
  return this.post(`/app/${appId}/api/groups`, data);
@@ -1466,7 +1564,16 @@ export class ApiClient {
1466
1564
  // ============================================
1467
1565
  async listGroupMembers(appId, groupType, groupId, options) {
1468
1566
  const qs = options?.include ? `?include=${options.include}` : "";
1469
- return this.get(`/app/${appId}/api/groups/${groupType}/${groupId}/members${qs}`);
1567
+ const result = await this.get(`/app/${appId}/api/groups/${groupType}/${groupId}/members${qs}`);
1568
+ // #1316: prefer `nextCursor`; fall back to the deprecated `cursor` alias
1569
+ // so the CLI keeps paginating against pre-#1316 servers.
1570
+ const nextCursor = result?.nextCursor ?? result?.cursor;
1571
+ return {
1572
+ items: result?.items ?? [],
1573
+ nextCursor,
1574
+ hasMore: result?.hasMore ?? nextCursor != null,
1575
+ cursor: nextCursor,
1576
+ };
1470
1577
  }
1471
1578
  async addGroupMember(appId, groupType, groupId, data) {
1472
1579
  return this.post(`/app/${appId}/api/groups/${groupType}/${groupId}/members`, data);
@@ -1511,7 +1618,7 @@ export class ApiClient {
1511
1618
  const result = await this.get(`/app/${appId}/api/collections${q ? `?${q}` : ""}`);
1512
1619
  return {
1513
1620
  items: result?.items ?? [],
1514
- nextCursor: result?.cursor ?? null,
1621
+ nextCursor: result?.nextCursor ?? result?.cursor ?? null,
1515
1622
  };
1516
1623
  }
1517
1624
  async listAllCollections(appId, params) {
@@ -1524,7 +1631,7 @@ export class ApiClient {
1524
1631
  const result = await this.get(`/app/${appId}/api/admin/collections${q ? `?${q}` : ""}`);
1525
1632
  return {
1526
1633
  items: result?.items ?? [],
1527
- nextCursor: result?.cursor ?? null,
1634
+ nextCursor: result?.nextCursor ?? result?.cursor ?? null,
1528
1635
  };
1529
1636
  }
1530
1637
  async createCollection(appId, data) {
@@ -1552,7 +1659,7 @@ export class ApiClient {
1552
1659
  const result = await this.get(`/app/${appId}/api/collections/${collectionId}/documents${q ? `?${q}` : ""}`);
1553
1660
  return {
1554
1661
  items: result?.items ?? [],
1555
- nextCursor: result?.cursor ?? null,
1662
+ nextCursor: result?.nextCursor ?? result?.cursor ?? null,
1556
1663
  };
1557
1664
  }
1558
1665
  async addCollectionDocument(appId, collectionId, data) {
@@ -1571,7 +1678,7 @@ export class ApiClient {
1571
1678
  const result = await this.get(`/app/${appId}/api/documents/${documentId}/collections${q ? `?${q}` : ""}`);
1572
1679
  return {
1573
1680
  items: result?.items ?? [],
1574
- nextCursor: result?.cursor ?? null,
1681
+ nextCursor: result?.nextCursor ?? result?.cursor ?? null,
1575
1682
  };
1576
1683
  }
1577
1684
  // ============================================
@@ -1841,7 +1948,16 @@ export class ApiClient {
1841
1948
  return this.delete(`/app/${appId}/api/blob-buckets/${encodeURIComponent(bucketIdOrKey)}`);
1842
1949
  }
1843
1950
  async listBucketBlobs(appId, bucketIdOrKey, params) {
1844
- return this.get(`/app/${appId}/api/blob-buckets/${encodeURIComponent(bucketIdOrKey)}/blobs`, params);
1951
+ const result = await this.get(`/app/${appId}/api/blob-buckets/${encodeURIComponent(bucketIdOrKey)}/blobs`, params);
1952
+ // #1316: prefer `nextCursor`; fall back to the deprecated `cursor` alias
1953
+ // so the CLI keeps paginating against pre-#1316 servers.
1954
+ const nextCursor = result?.nextCursor ?? result?.cursor;
1955
+ return {
1956
+ items: result?.items ?? [],
1957
+ nextCursor,
1958
+ hasMore: result?.hasMore ?? nextCursor != null,
1959
+ cursor: nextCursor,
1960
+ };
1845
1961
  }
1846
1962
  async uploadBucketBlob(appId, bucketIdOrKey, data, meta) {
1847
1963
  const credentials = await this.ensureAuthenticated();
@@ -1896,6 +2012,12 @@ export class ApiClient {
1896
2012
  expiresInSeconds: expiresInSeconds || 300,
1897
2013
  });
1898
2014
  }
2015
+ // Blob metadata (#1966): head a single blob without downloading its bytes.
2016
+ // Mirrors listBucketBlobs/downloadBucketBlob addressing; the server heads the
2017
+ // R2 object and returns the serialized BlobInfo shape.
2018
+ async getBucketBlobMetadata(appId, bucketIdOrKey, blobId) {
2019
+ return this.get(`/app/${appId}/api/blob-buckets/${encodeURIComponent(bucketIdOrKey)}/blobs/${blobId}/metadata`);
2020
+ }
1899
2021
  }
1900
2022
  // Export a singleton instance
1901
2023
  export const apiClient = new ApiClient();