@recursiv/sdk 0.7.51 → 0.7.53

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 (37) hide show
  1. package/README.md +43 -5
  2. package/dist/client.d.ts +6 -2
  3. package/dist/client.js +10 -9
  4. package/dist/index.d.ts +15 -4
  5. package/dist/index.js +11 -1
  6. package/dist/resources/admin.d.ts +13 -0
  7. package/dist/resources/agents.d.ts +13 -2
  8. package/dist/resources/agents.js +7 -1
  9. package/dist/resources/app-subscriptions.d.ts +48 -2
  10. package/dist/resources/app-subscriptions.js +18 -2
  11. package/dist/resources/bookmarks.d.ts +172 -0
  12. package/dist/resources/bookmarks.js +44 -0
  13. package/dist/resources/boosts.d.ts +23 -1
  14. package/dist/resources/boosts.js +6 -1
  15. package/dist/resources/communities.d.ts +12 -2
  16. package/dist/resources/communities.js +15 -1
  17. package/dist/resources/curator.d.ts +24 -6
  18. package/dist/resources/curator.js +2 -1
  19. package/dist/resources/discovery.d.ts +104 -2
  20. package/dist/resources/discovery.js +111 -20
  21. package/dist/resources/invite-codes-admin.d.ts +13 -0
  22. package/dist/resources/invite-codes.d.ts +10 -0
  23. package/dist/resources/invite-codes.js +6 -6
  24. package/dist/resources/link-preview.d.ts +11 -4
  25. package/dist/resources/link-preview.js +4 -3
  26. package/dist/resources/notifications.d.ts +26 -0
  27. package/dist/resources/posts.d.ts +45 -6
  28. package/dist/resources/posts.js +29 -4
  29. package/dist/resources/profiles.d.ts +23 -14
  30. package/dist/resources/profiles.js +18 -5
  31. package/dist/resources/protocols.d.ts +54 -7
  32. package/dist/resources/protocols.js +10 -4
  33. package/dist/resources/search.d.ts +159 -0
  34. package/dist/resources/search.js +21 -0
  35. package/dist/types.d.ts +146 -5
  36. package/llm.md +41 -8
  37. package/package.json +2 -2
@@ -0,0 +1,172 @@
1
+ import type { HttpClient } from '../client.js';
2
+ import type { PaginatedResponse, PaginationMeta, Post, SingleResponse } from '../types.js';
3
+ /**
4
+ * What a bookmark is stored under: `post:<post id>`, `social:<sha256>` (the hash the member's source
5
+ * controls use for that open-network post) or `article:<sha256 of the url>`. Every save, list and keys
6
+ * call returns it; pass it back to `unsave`.
7
+ */
8
+ export type BookmarkKey = string;
9
+ export type BookmarkKind = 'post' | 'social' | 'article';
10
+ export type BookmarkProtocol = 'bluesky' | 'activitypub' | 'nostr';
11
+ /** Something to save: a Minds post, an open-network post (with the snapshot to show it by) or an article. */
12
+ export type BookmarkInput = {
13
+ kind: 'post';
14
+ post_id: string;
15
+ } | {
16
+ kind: 'social';
17
+ protocol: BookmarkProtocol;
18
+ /** The network's immutable post id: an at:// URI, an ActivityPub object URL, or a Nostr event id. */
19
+ native_id: string;
20
+ /** ActivityPub only: the Mastodon-compatible status id that reopens the conversation. Dropped elsewhere. */
21
+ read_ref?: string;
22
+ author: {
23
+ display_name: string;
24
+ handle: string;
25
+ /** The author's immutable id (DID, actor URL, pubkey), so the member's blocks and mutes apply. Derived on Bluesky. */
26
+ native_id?: string;
27
+ };
28
+ /** At most 500 characters. */
29
+ snippet: string;
30
+ } | {
31
+ kind: 'article';
32
+ /** An http(s) URL without credentials, at most 2048 characters. */
33
+ url: string;
34
+ title: string;
35
+ outlet: string;
36
+ /** A World Today story or edition id. */
37
+ story?: string;
38
+ /** An https image URL. */
39
+ image?: string;
40
+ };
41
+ /** A device's saved item for `merge`: an input plus when the device saved it (held to the last five years, never the future). */
42
+ export type BookmarkMergeItem = BookmarkInput & {
43
+ saved_at?: string;
44
+ };
45
+ export interface SavedBookmark {
46
+ key: BookmarkKey;
47
+ kind: BookmarkKind;
48
+ saved: true;
49
+ /** False when it was already saved: nothing changed, not even `saved_at`. */
50
+ created: boolean;
51
+ saved_at: string | null;
52
+ /** Posts only: the post stored. A bare repost is saved as its original, so use this id for its saved state. */
53
+ post_id?: string;
54
+ }
55
+ export interface UnsavedBookmark {
56
+ key: BookmarkKey;
57
+ saved: false;
58
+ /** False when it was not saved; unsaving is idempotent. */
59
+ removed: boolean;
60
+ }
61
+ /** A saved open-network post or article, as the snapshot taken when it was saved. */
62
+ export type BookmarkItem = {
63
+ key: BookmarkKey;
64
+ kind: 'social';
65
+ saved_at: string;
66
+ social: {
67
+ protocol: BookmarkProtocol;
68
+ native_id: string;
69
+ read_ref: string | null;
70
+ author: {
71
+ display_name: string;
72
+ handle: string;
73
+ native_id: string | null;
74
+ };
75
+ snippet: string;
76
+ };
77
+ } | {
78
+ key: BookmarkKey;
79
+ kind: 'article';
80
+ saved_at: string;
81
+ article: {
82
+ url: string;
83
+ title: string;
84
+ outlet: string;
85
+ story: string | null;
86
+ image: string | null;
87
+ };
88
+ };
89
+ /** One saved key and the identity it stands for, for an app's synchronous saved-state cache. */
90
+ export interface BookmarkKeyRow {
91
+ key: BookmarkKey;
92
+ kind: BookmarkKind;
93
+ saved_at: string;
94
+ post_id?: string;
95
+ protocol?: BookmarkProtocol;
96
+ native_id?: string;
97
+ url?: string;
98
+ }
99
+ export interface BookmarkMergeResult {
100
+ saved: number;
101
+ already: number;
102
+ skipped: number;
103
+ over_limit: number;
104
+ /** What the member has saved in this app now. */
105
+ total: number;
106
+ /** The most one member keeps per app. */
107
+ limit: number;
108
+ /** One per item, in input order. */
109
+ results: Array<{
110
+ key: BookmarkKey | null;
111
+ status: 'saved' | 'already' | 'skipped' | 'over_limit';
112
+ reason?: 'invalid' | 'unavailable';
113
+ }>;
114
+ }
115
+ /** A saved Minds post, hydrated like a feed post, with when the member saved it. */
116
+ export type BookmarkedPost = Post & {
117
+ bookmarked_at: string | null;
118
+ };
119
+ export interface BookmarksPage<T> extends PaginatedResponse<T> {
120
+ /** Page on `next_cursor` until it is null: a page can come back short while it skips posts the member can no longer read. */
121
+ meta: PaginationMeta & {
122
+ next_cursor: string | null;
123
+ };
124
+ }
125
+ /** Most items one `merge` call takes. */
126
+ export declare const BOOKMARK_MERGE_MAX_ITEMS = 200;
127
+ /**
128
+ * `retry: false` answers after one attempt. Use it for the first call that finds out whether synced
129
+ * bookmarks exist: a 503 `bookmarks_unavailable` is otherwise retried with backoff like any 5xx.
130
+ */
131
+ export interface BookmarkRequestOptions {
132
+ retry?: boolean;
133
+ }
134
+ /**
135
+ * Synced bookmarks: the signed-in member's saved Minds posts, open-network posts and articles in this app,
136
+ * across every device they sign in on. Private to the member; needs the member's own app key.
137
+ *
138
+ * Until the server has synced bookmarks every call rejects with a 503 whose code is `bookmarks_unavailable`:
139
+ * keep bookmarks on the device then. Keys that are not a member's own app key get 403 `project_binding_required`.
140
+ */
141
+ export declare class BookmarksResource {
142
+ private client;
143
+ constructor(client: HttpClient);
144
+ /** Save one item. Idempotent: saving what is saved changes nothing. 409 `bookmark_limit` past the member's limit. */
145
+ save(item: BookmarkInput, options?: BookmarkRequestOptions): Promise<SingleResponse<SavedBookmark>>;
146
+ /** Unsave by the key a save, list or keys call returned, or by `{ postId }`. Idempotent. */
147
+ unsave(target: BookmarkKey | {
148
+ postId: string;
149
+ }, options?: BookmarkRequestOptions): Promise<SingleResponse<UnsavedBookmark>>;
150
+ /** Saved Minds posts, newest saved first, hydrated like the feed. Page on `meta.next_cursor`. */
151
+ posts(params?: {
152
+ limit?: number;
153
+ cursor?: string;
154
+ }, options?: BookmarkRequestOptions): Promise<BookmarksPage<BookmarkedPost>>;
155
+ /** Saved open-network posts and articles, newest saved first. Page on `meta.next_cursor`. */
156
+ list(params?: {
157
+ kind?: 'social' | 'article';
158
+ limit?: number;
159
+ cursor?: string;
160
+ }, options?: BookmarkRequestOptions): Promise<BookmarksPage<BookmarkItem>>;
161
+ /** Every saved key (hidden posts included), newest saved first, up to 500 a page. For a saved-state cache. */
162
+ keys(params?: {
163
+ limit?: number;
164
+ cursor?: string;
165
+ }, options?: BookmarkRequestOptions): Promise<BookmarksPage<BookmarkKeyRow>>;
166
+ /**
167
+ * Upload a device's saves, e.g. when its member signs in: at most 200 items a call. Idempotent (a second
168
+ * merge of the same items is a no-op); invalid items and posts the member cannot read are `skipped`, and
169
+ * items past the member's limit come back `over_limit` (the newest are kept).
170
+ */
171
+ merge(items: BookmarkMergeItem[], options?: BookmarkRequestOptions): Promise<SingleResponse<BookmarkMergeResult>>;
172
+ }
@@ -0,0 +1,44 @@
1
+ /** Most items one `merge` call takes. */
2
+ export const BOOKMARK_MERGE_MAX_ITEMS = 200;
3
+ /**
4
+ * Synced bookmarks: the signed-in member's saved Minds posts, open-network posts and articles in this app,
5
+ * across every device they sign in on. Private to the member; needs the member's own app key.
6
+ *
7
+ * Until the server has synced bookmarks every call rejects with a 503 whose code is `bookmarks_unavailable`:
8
+ * keep bookmarks on the device then. Keys that are not a member's own app key get 403 `project_binding_required`.
9
+ */
10
+ export class BookmarksResource {
11
+ client;
12
+ constructor(client) {
13
+ this.client = client;
14
+ }
15
+ /** Save one item. Idempotent: saving what is saved changes nothing. 409 `bookmark_limit` past the member's limit. */
16
+ save(item, options) {
17
+ return this.client.put('/bookmarks', item, options);
18
+ }
19
+ /** Unsave by the key a save, list or keys call returned, or by `{ postId }`. Idempotent. */
20
+ unsave(target, options) {
21
+ const key = typeof target === 'string' ? target : `post:${target.postId}`;
22
+ return this.client.delete(`/bookmarks/${encodeURIComponent(key)}`, options);
23
+ }
24
+ /** Saved Minds posts, newest saved first, hydrated like the feed. Page on `meta.next_cursor`. */
25
+ posts(params, options) {
26
+ return this.client.get('/posts', { bookmarked: true, ...params }, options);
27
+ }
28
+ /** Saved open-network posts and articles, newest saved first. Page on `meta.next_cursor`. */
29
+ list(params, options) {
30
+ return this.client.get('/bookmarks', params, options);
31
+ }
32
+ /** Every saved key (hidden posts included), newest saved first, up to 500 a page. For a saved-state cache. */
33
+ keys(params, options) {
34
+ return this.client.get('/bookmarks/keys', params, options);
35
+ }
36
+ /**
37
+ * Upload a device's saves, e.g. when its member signs in: at most 200 items a call. Idempotent (a second
38
+ * merge of the same items is a no-op); invalid items and posts the member cannot read are `skipped`, and
39
+ * items past the member's limit come back `over_limit` (the newest are kept).
40
+ */
41
+ merge(items, options) {
42
+ return this.client.post('/bookmarks/merge', { items }, options);
43
+ }
44
+ }
@@ -44,10 +44,27 @@ export interface BoostBalance {
44
44
  /** The 50 most recent balance movements, newest first. */
45
45
  ledger: BoostLedgerEntry[];
46
46
  }
47
+ /** The boost credit the key's app includes with its paid plans. */
48
+ export interface BoostPlanCredit {
49
+ /** Micro-USD each paid billing period of a plan adds, by tier key (e.g. `plus`, `pro`). */
50
+ per_period_micros: Record<string, number>;
51
+ /** What your current plan on this app adds each paid period; 0 without one. */
52
+ current_micros: number;
53
+ /** When your next paid period starts and adds `current_micros`; null when none is due. */
54
+ next_grant_at: string | null;
55
+ /**
56
+ * Plan credit you owe: taken back after your plan dropped or ended inside a period, but already
57
+ * spent. Your next plan credit repays it before adding anything. 0 when nothing is owed
58
+ * (absent from older servers).
59
+ */
60
+ owed_micros?: number;
61
+ }
47
62
  export interface BoostBalanceResponse extends SingleResponse<BoostBalance> {
63
+ /** `plan_credit` is null when the app's plans include no boost credit (absent from older servers). */
48
64
  meta: {
49
65
  min_cpm_micros: number;
50
66
  topup?: BoostTopupLimits;
67
+ plan_credit?: BoostPlanCredit | null;
51
68
  };
52
69
  }
53
70
  export interface BoostCampaignResponse extends SingleResponse<BoostCampaign> {
@@ -98,7 +115,10 @@ export declare class BoostsResource {
98
115
  limit?: number;
99
116
  offset?: number;
100
117
  }): Promise<PaginatedResponse<BoostCampaign>>;
101
- /** Your balance, recent ledger, the CPM floor, and the top-up limits. */
118
+ /**
119
+ * Your balance, recent ledger, the CPM floor, the top-up limits, and the boost credit your
120
+ * plan on this app adds each paid period (`meta.plan_credit`, a `grant` ledger row per period).
121
+ */
102
122
  balance(): Promise<BoostBalanceResponse>;
103
123
  /** Pause one of your active campaigns. */
104
124
  pause(campaignId: string): Promise<BoostCampaignResponse>;
@@ -112,6 +132,8 @@ export declare class BoostsResource {
112
132
  * Buy boost credit: opens a one-time Stripe Checkout for `amount_cents` (USD) on the app's
113
133
  * own Stripe account. The balance is credited once the payment succeeds. Limits are in
114
134
  * `balance().meta.topup`. Requires a project-scoped key with `billing:write`.
135
+ * `return_url` keeps its own query and fragment; `topup=success&session_id=<id>` or
136
+ * `topup=cancelled` is added to its query (to the fragment's query for a hash route).
115
137
  */
116
138
  topup(input: {
117
139
  amount_cents: number;
@@ -20,7 +20,10 @@ export class BoostsResource {
20
20
  mine(params) {
21
21
  return this.client.get('/boosts/mine', params);
22
22
  }
23
- /** Your balance, recent ledger, the CPM floor, and the top-up limits. */
23
+ /**
24
+ * Your balance, recent ledger, the CPM floor, the top-up limits, and the boost credit your
25
+ * plan on this app adds each paid period (`meta.plan_credit`, a `grant` ledger row per period).
26
+ */
24
27
  balance() {
25
28
  return this.client.get('/boosts/balance');
26
29
  }
@@ -44,6 +47,8 @@ export class BoostsResource {
44
47
  * Buy boost credit: opens a one-time Stripe Checkout for `amount_cents` (USD) on the app's
45
48
  * own Stripe account. The balance is credited once the payment succeeds. Limits are in
46
49
  * `balance().meta.topup`. Requires a project-scoped key with `billing:write`.
50
+ * `return_url` keeps its own query and fragment; `topup=success&session_id=<id>` or
51
+ * `topup=cancelled` is added to its query (to the fragment's query for a hash route).
47
52
  */
48
53
  topup(input) {
49
54
  return this.client.post('/boosts/topup', input, { retry: false });
@@ -1,5 +1,5 @@
1
1
  import type { HttpClient } from '../client.js';
2
- import type { Community, CommunityBannedMember, CommunityCard, CommunityDetail, CommunityMember, CommunityPeopleParams, CreateCommunityInput, CreatedCommunity, JoinResult, LeaveResult, ListCommunitiesParams, PaginatedResponse, PaginationParams, RelatedCommunity, SingleResponse } from '../types.js';
2
+ import type { AnswerJoinRequestsInput, ApprovedJoinRequests, Community, CommunityBannedMember, CommunityCard, CommunityDetail, CommunityMember, CommunityPeopleParams, CreateCommunityInput, CreatedCommunity, DeclinedJoinRequests, JoinResult, LeaveResult, ListCommunitiesParams, PaginatedResponse, PaginationParams, RelatedCommunity, SingleResponse } from '../types.js';
3
3
  export declare class CommunitiesResource {
4
4
  private client;
5
5
  constructor(client: HttpClient);
@@ -25,7 +25,7 @@ export declare class CommunitiesResource {
25
25
  }): Promise<PaginatedResponse<RelatedCommunity>>;
26
26
  /** List members of a community. `q` searches by name or username. */
27
27
  members(id: string, params?: CommunityPeopleParams): Promise<PaginatedResponse<CommunityMember>>;
28
- /** Create a new community */
28
+ /** Create a new community. Omit slug for a server-generated collision-resistant slug. */
29
29
  create(input: CreateCommunityInput): Promise<SingleResponse<CreatedCommunity>>;
30
30
  /** Join a community */
31
31
  join(id: string): Promise<SingleResponse<JoinResult>>;
@@ -52,6 +52,16 @@ export declare class CommunitiesResource {
52
52
  declineMember(id: string, userId: string): Promise<SingleResponse<{
53
53
  declined: boolean;
54
54
  }>>;
55
+ /**
56
+ * Let many pending members in at once. Admin or higher. Pass `{ user_ids }`
57
+ * (at most 100) or `{ all: true }` for the oldest 100 waiting; call again
58
+ * while `remaining` is above 0. Named people with no pending request (already
59
+ * in, already answered, banned, never asked) come back in `skipped`. A ban is
60
+ * never answered.
61
+ */
62
+ approveMembers(id: string, input: AnswerJoinRequestsInput): Promise<SingleResponse<ApprovedJoinRequests>>;
63
+ /** Turn many pending members away at once, as `approveMembers` lets them in. Admin or higher. */
64
+ declineMembers(id: string, input: AnswerJoinRequestsInput): Promise<SingleResponse<DeclinedJoinRequests>>;
55
65
  /** Remove a member. Moderator or higher, and only members below your role. */
56
66
  removeMember(id: string, userId: string): Promise<SingleResponse<{
57
67
  removed: boolean;
@@ -33,7 +33,7 @@ export class CommunitiesResource {
33
33
  members(id, params) {
34
34
  return this.client.get(`/communities/${id}/members`, params);
35
35
  }
36
- /** Create a new community */
36
+ /** Create a new community. Omit slug for a server-generated collision-resistant slug. */
37
37
  create(input) {
38
38
  return this.client.post('/communities', input);
39
39
  }
@@ -70,6 +70,20 @@ export class CommunitiesResource {
70
70
  declineMember(id, userId) {
71
71
  return this.client.post(`/communities/${id}/members/${userId}/decline`);
72
72
  }
73
+ /**
74
+ * Let many pending members in at once. Admin or higher. Pass `{ user_ids }`
75
+ * (at most 100) or `{ all: true }` for the oldest 100 waiting; call again
76
+ * while `remaining` is above 0. Named people with no pending request (already
77
+ * in, already answered, banned, never asked) come back in `skipped`. A ban is
78
+ * never answered.
79
+ */
80
+ approveMembers(id, input) {
81
+ return this.client.post(`/communities/${encodeURIComponent(id)}/requests/approve`, input);
82
+ }
83
+ /** Turn many pending members away at once, as `approveMembers` lets them in. Admin or higher. */
84
+ declineMembers(id, input) {
85
+ return this.client.post(`/communities/${encodeURIComponent(id)}/requests/decline`, input);
86
+ }
73
87
  /** Remove a member. Moderator or higher, and only members below your role. */
74
88
  removeMember(id, userId) {
75
89
  return this.client.delete(`/communities/${id}/members/${userId}`);
@@ -1,5 +1,23 @@
1
1
  import type { HttpClient } from '../client.js';
2
- import type { PaginatedResponse, Post, SingleResponse } from '../types.js';
2
+ import type { DiscoveryLayout, PaginatedResponse, PaginationMeta, Post, SingleResponse } from '../types.js';
3
+ /** Which surface is asking: Home's For You (default) or Discover's For you. */
4
+ export type ForYouSurface = 'home' | 'discover';
5
+ export type ForYouParams = {
6
+ limit?: number;
7
+ offset?: number;
8
+ /** The previous page's `meta.next_cursor`: pages a stored ranking, so posts read meanwhile never shift the next page. */
9
+ cursor?: string;
10
+ /** Adds `meta.discovery` (layouts) and the public safety floor; limit <= 25. */
11
+ discovery?: boolean;
12
+ /** `discover` ranks for Discover: fresher, beyond the member's follows, with their category order. */
13
+ surface?: ForYouSurface;
14
+ };
15
+ export interface ForYouMeta extends PaginationMeta {
16
+ discovery?: DiscoveryLayout;
17
+ next_cursor?: string;
18
+ /** Whether the ranker can personalize for this member yet (3 follows or 3 upvoted authors). */
19
+ personalized?: boolean;
20
+ }
3
21
  /** RSS / Atom source. Default — `type` may be omitted for back-compat. */
4
22
  export interface CuratorRssSource {
5
23
  type?: 'rss';
@@ -250,10 +268,10 @@ export declare class CuratorResource {
250
268
  * existing posts for the signed-in user (engagement + recency + social +
251
269
  * affinity, no model calls). Returns posts in the same shape as
252
270
  * `sdk.posts.list()`, so any feed UI renders it unchanged. Requires a
253
- * project-scoped key (the feed is per-app).
271
+ * project-scoped key (the feed is per-app). Servers that predate `cursor`,
272
+ * `discovery` and `surface` ignore them.
254
273
  */
255
- forYou(params?: {
256
- limit?: number;
257
- offset?: number;
258
- }): Promise<PaginatedResponse<Post>>;
274
+ forYou(params?: ForYouParams): Promise<PaginatedResponse<Post> & {
275
+ meta: ForYouMeta;
276
+ }>;
259
277
  }
@@ -47,7 +47,8 @@ export class CuratorResource {
47
47
  * existing posts for the signed-in user (engagement + recency + social +
48
48
  * affinity, no model calls). Returns posts in the same shape as
49
49
  * `sdk.posts.list()`, so any feed UI renders it unchanged. Requires a
50
- * project-scoped key (the feed is per-app).
50
+ * project-scoped key (the feed is per-app). Servers that predate `cursor`,
51
+ * `discovery` and `surface` ignore them.
51
52
  */
52
53
  forYou(params = {}) {
53
54
  return this.client.get('/curator/foryou', params);
@@ -1,8 +1,71 @@
1
+ import type { HttpClient } from '../client.js';
1
2
  import type { DiscoveryLayout, Post } from '../types.js';
3
+ import type { CuratorResource } from './curator.js';
2
4
  import type { DiscoveryLearning } from './discovery-learning.js';
3
5
  import type { PostsResource } from './posts.js';
4
6
  import type { ProtocolsResource, SocialNetwork, SocialPost, SocialRadar, SocialRadarPosts } from './protocols.js';
5
7
  export type DiscoveryRecommendationKind = 'groups' | 'people' | 'tags';
8
+ /** What GET /discover/recommendations suggests: members, groups, tags, open-network accounts or World Today stories. */
9
+ export type EntityKind = 'member' | 'group' | 'tag' | 'network_account' | 'story';
10
+ /**
11
+ * One suggestion. `key` is the entity's id (a uuid), or `<protocol>:<native id>` for an open-network account.
12
+ * The public display fields depend on the kind: members carry id, username, name, image, bio and
13
+ * follower_count; groups id, name, slug, image, description and member_count; tags id, name and slug;
14
+ * open-network accounts protocol, actor, handle, display_name and avatar_url (never a link out); stories id,
15
+ * headline, kicker, story_kind and edition_date.
16
+ */
17
+ export interface EntityRecommendation {
18
+ kind: EntityKind;
19
+ key: string;
20
+ /** active: posted in the last 7 days or 4+ times in 30 (for groups, 3+ distinct posters in 30 days); warm: activity in 90 days. */
21
+ tier: 'active' | 'warm';
22
+ score: number;
23
+ /** The entity's share of the requested category, or null for category=all. */
24
+ share: number | null;
25
+ last_active_at: string | null;
26
+ posts_30d: number;
27
+ /** Distinct posters in 30 days (groups and tags), otherwise null. */
28
+ authors_30d: number | null;
29
+ /** Up to three browse categories, largest share first. */
30
+ categories: Array<{
31
+ category: string;
32
+ share: number;
33
+ }>;
34
+ [display: string]: unknown;
35
+ }
36
+ export interface EntityRecommendationsParams {
37
+ kind: EntityKind;
38
+ /** 'all' (default) or a browse category id. */
39
+ category?: string;
40
+ /** 1..50, default 10. */
41
+ limit?: number;
42
+ /** A per-session token ([A-Za-z0-9_-], up to 64): the same token keeps the order stable; a new one rotates near-ties. */
43
+ session?: string;
44
+ /** Up to 50 keys of the kind to leave out, e.g. open-network accounts the member follows through a connected account. */
45
+ exclude?: string[];
46
+ }
47
+ export interface EntityRecommendationsResponse {
48
+ data: EntityRecommendation[];
49
+ meta: {
50
+ kind: EntityKind;
51
+ category: string;
52
+ as_of: string | null;
53
+ /** Why the list is empty: serving is off, the key's app is not the one signals are built for, no snapshot yet, the viewer's follows could not be checked, or the key has no app. */
54
+ reason?: 'disabled' | 'other_app' | 'no_snapshot' | 'viewer_facts_unavailable' | 'no_app';
55
+ /** Suggestions left out because they are resting for this viewer, and because the viewer follows, joined, blocked or muted them. */
56
+ resting: number;
57
+ excluded: number;
58
+ /** True when the member's own category order lifted suggestions in their first categories. */
59
+ personalized: boolean;
60
+ };
61
+ }
62
+ export interface EntityImpressionsInput {
63
+ kind: EntityKind;
64
+ /** 1..50 keys of the kind. */
65
+ keys: string[];
66
+ /** 'shown' when a suggestion was on screen; 'engaged' on a tap, follow or join. Three showings without a tap rest a suggestion for fourteen days; engaging clears it. */
67
+ event: 'shown' | 'engaged';
68
+ }
6
69
  export interface DiscoveryGroupCandidate {
7
70
  id: string;
8
71
  name: string;
@@ -161,7 +224,7 @@ export interface DiscoveryFeedPage {
161
224
  export declare const GALLERY_MAX_ITEMS = 12;
162
225
  /** Per-viewer feed session. Create a new session when user, app, category or
163
226
  * source changes. All reads retain existing server authorization/moderation.
164
- * At most four requests per page; providers fail independently. */
227
+ * At most four sources per page (a topics network's read walks up to TOPIC_WALK topic pages); providers fail independently. */
165
228
  export declare class DiscoveryFeedSession {
166
229
  private posts;
167
230
  private protocols;
@@ -186,6 +249,15 @@ export declare class DiscoveryFeedSession {
186
249
  retryFailed(): void;
187
250
  next(): Promise<DiscoveryFeedPage>;
188
251
  private read;
252
+ /**
253
+ * One read of a topics network: the next topic page that carries posts. A topic the provider no
254
+ * longer lists (Bluesky rotates its trends between the catalog read and the walk through it) is
255
+ * that topic's end, not the network's failure; a topic page with nothing in the category is
256
+ * walked past too, and its continuation kept, so the page reaches a topic that carries posts
257
+ * instead of spending a round on each empty one. Bounded by TOPIC_WALK reads and TOPIC_WALK_MS;
258
+ * a walk that stops short has moved the stream on, and the next read continues from there.
259
+ */
260
+ private walkTopics;
189
261
  private readPage;
190
262
  /** Page 0: the top breaking cluster, else the top cluster. Every second later page: the next unused story or viral cluster. */
191
263
  private radarCluster;
@@ -201,7 +273,37 @@ export declare class DiscoveryFeedSession {
201
273
  export declare class DiscoveryResource {
202
274
  private posts;
203
275
  private protocols;
204
- constructor(posts: PostsResource, protocols: ProtocolsResource);
276
+ private curator?;
277
+ private http?;
278
+ constructor(posts: PostsResource, protocols: ProtocolsResource, curator?: Pick<CuratorResource, "forYou"> | undefined, http?: Pick<HttpClient, "get" | "post"> | undefined);
279
+ /**
280
+ * Suggestions for the signed-in viewer from the app's one recommender: active or warm members, groups
281
+ * with several posters, tags used by several people, open-network accounts or stories, overall or in one
282
+ * browse category. What the viewer follows, joined, blocked or muted is left out, and so is anything
283
+ * resting for them. Call `recordImpressions` when suggestions are actually on screen and when one is tapped.
284
+ */
285
+ recommendations(params: EntityRecommendationsParams, options?: {
286
+ retry?: boolean;
287
+ }): Promise<EntityRecommendationsResponse>;
288
+ /** Records that suggestions were shown (at most once per half hour counts) or engaged with. */
289
+ recordImpressions(input: EntityImpressionsInput): Promise<{
290
+ data: {
291
+ recorded: number;
292
+ reason?: 'disabled' | 'other_app' | 'unavailable' | 'no_app';
293
+ };
294
+ }>;
295
+ /**
296
+ * A For you Discover feed: the member's For You ranking (asked for Discover's surface, with layouts and a
297
+ * snapshot cursor) in a session's Minds slot. With a category, the session also reads open-network posts
298
+ * from that category (the member's top one); without, it is Minds only. Never the radar. The page-0
299
+ * arrangement and when to use which are the app's choice.
300
+ */
301
+ createPersonalFeed(options: {
302
+ networks: SocialNetwork[];
303
+ learning?: DiscoveryLearning;
304
+ recommend?: DiscoveryRecommendationSources;
305
+ category?: string;
306
+ }): DiscoveryFeedSession;
205
307
  createFeed(options: {
206
308
  networks: SocialNetwork[];
207
309
  category?: string;