@recursiv/sdk 0.7.52 → 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.
package/README.md CHANGED
@@ -93,7 +93,8 @@ for await (const chunk of r.agents.chatStream(agent.id, { message: 'Hello!' }))
93
93
  ### Social
94
94
  | Resource | Key methods |
95
95
  |----------|------------|
96
- | `r.posts` | `create`, `update`, `list`, `liked`, `search`, `replies`, `react` |
96
+ | `r.posts` | `create`, `update`, `list`, `liked`, `bookmarked`, `bookmark`, `unbookmark`, `search`, `replies`, `react` |
97
+ | `r.bookmarks` | `save`, `unsave`, `posts`, `list`, `keys`, `merge` — the member's saved posts, open-network posts and articles, synced across devices |
97
98
  | `r.communities` | `create`, `join`, `search`, `members` |
98
99
  | `r.chat` | `send`, `dm`, `createGroup`, `messages` (cursor paging), `acceptRequest`, `declineRequest`, `leave`, `deleteConversation` |
99
100
  | `r.tags` | `create`, `list` |
@@ -114,7 +115,7 @@ for await (const chunk of r.agents.chatStream(agent.id, { message: 'Hello!' }))
114
115
  | `r.admin` | User management, stats, content reports (`listReports`, `resolveReport`, `dismissReport`) |
115
116
  | `r.dispatcher` | Task queue, claims, signals, outcomes, webhooks |
116
117
  | `r.goals` | Org goals with task rollups (group dispatcher tasks) |
117
- | `r.protocols` | Private Minds safety: `sourceControls` (current enablement and saved choices; filter with `control`, each item may carry a `display` label), `setSourceControl`, `onSourceControlChange`, `reportSource`. Delegated accounts: `nativeSocialAccounts`, `startNativeSocialConnection`, `finishNativeSocialConnection`, `disconnectNativeSocialAccount`. Native follows: `nativeFollowStates` (every connected account's state for one source, one read), `nativeFollowState`, `prepareNativeFollow`, `submitNativeFollow`, `nativeFollowStatus` (native following or pending approval, distinct from a Minds feed subscription; preserve the complete signed Nostr list). Native reposts and undo: `nativeRepostState`, `prepareNativeRepost`, `submitNativeRepost`, `nativeRepostStatus` (public posts, explicit approval, connected account, no uncertain retries). Native likes and unlikes: `nativeLikeState`, `prepareNativeLike`, `submitNativeLike`, `nativeLikeStatus` (connected authority, app enablement and explicit approval; resume pending actions instead of duplicating them). Reviewed replies and optional linked posts: `prepareNativeReply`, `submitNativeReply`, `nativeReplyStatus`, `nativeReplyHistory`, `prepareNativeReplyRemoval`, `removeNativeReply` (require deployed API support and app enablement). Keyless shared post links: `sharedNetworks(projectId)`, `getSharedConversation(projectId, { protocol, post, readRef? })`. Member public reading: `searchPublicPosts` (selected account required for Bluesky/ActivityPub; public Nostr search), `getConnectedSocialConversation` (preserve result readerAccountId/readRef), `publicNetworks`, `searchPublicProfiles`, `getPublicProfile`, `getPublicAuthorFeed`, `getFollowingFeed`, `getPublicConversation`, `getPublicTrends`, `getPublicTrendFeed`; radar stories: `getPublicRadarStory` (one story by id with its posts), keyless `getSharedRadarStory(projectId, { cluster })` and `getSharedRadarPosts(projectId, { cluster })`; private app subscriptions: `listSubscriptions`, `subscribeToProfile`, `unsubscribeFromProfile`, `onSubscriptionChange`. `list` returns bounded adapter `maturity`, `limitations`, and exhaustive `supported`/`planned`/`unsupported` implementation capabilities (not provider health or enablement); settings, candidate refresh/status, search, and read-only quarantined candidate review via `listCandidates` / `getCandidate` |
118
+ | `r.protocols` | Private Minds safety: `sourceControls` (current enablement and saved choices; filter with `control`, each item may carry a `display` label), `setSourceControl`, `onSourceControlChange`, `reportSource`. Delegated accounts: `nativeSocialAccounts`, `startNativeSocialConnection`, `finishNativeSocialConnection`, `disconnectNativeSocialAccount`. Native follows: `nativeFollowStates` (every connected account's state for one source, one read), `nativeFollowState`, `prepareNativeFollow`, `submitNativeFollow`, `nativeFollowStatus` (native following or pending approval, distinct from a Minds feed subscription; preserve the complete signed Nostr list). Native reposts and undo: `nativeRepostState`, `prepareNativeRepost`, `submitNativeRepost`, `nativeRepostStatus` (public posts, explicit approval, connected account, no uncertain retries). Native likes and unlikes: `nativeLikeState`, `prepareNativeLike`, `submitNativeLike`, `nativeLikeStatus` (connected authority, app enablement and explicit approval; resume pending actions instead of duplicating them). Reviewed replies and optional linked posts: `prepareNativeReply`, `submitNativeReply`, `nativeReplyStatus`, `nativeReplyHistory`, `prepareNativeReplyRemoval`, `removeNativeReply` (require deployed API support and app enablement). Keyless shared post links: `sharedNetworks(projectId)`, `getSharedConversation(projectId, { protocol, post, readRef? })`. Member public reading: `searchPublicPosts` (selected account required for Bluesky/ActivityPub; public Nostr search), `getConnectedSocialConversation` (preserve result readerAccountId/readRef), `publicNetworks`, `searchPublicProfiles`, `getPublicProfile`, `getPublicAuthorFeed`, `getFollowingFeed`, `getPublicConversation`, `getPublicTrends`, `getPublicTrendFeed`; radar stories: `getPublicRadarStory` (one story by id with its posts; one that has left the radar comes with its archived copy and the live story it continues as), keyless `getSharedRadarStory(projectId, { cluster })` and `getSharedRadarPosts(projectId, { cluster })`; private app subscriptions: `listSubscriptions`, `subscribeToProfile`, `unsubscribeFromProfile`, `onSubscriptionChange`. `list` returns bounded adapter `maturity`, `limitations`, and exhaustive `supported`/`planned`/`unsupported` implementation capabilities (not provider health or enablement); settings, candidate refresh/status, search, and read-only quarantined candidate review via `listCandidates` / `getCandidate` |
118
119
 
119
120
  Full reference: see [llm.md](./llm.md) (ships with this package) or the type definitions in `dist/`.
120
121
 
@@ -648,7 +649,12 @@ per signed-in viewer and filter. Pass the app-advertised network catalog, then c
648
649
  `next()` as the reader approaches the end. Each page contains safe json-render
649
650
  module specs and native or protocol post objects. Preserve their existing source
650
651
  authority and interactive controls. `retryFailed()` explicitly retries unavailable
651
- sources; create a new session to refresh exhausted source windows.
652
+ sources; create a new session to refresh exhausted source windows. A topics network
653
+ (Bluesky) is read one topic page at a time; in a category the catalog's previews are not
654
+ judged to be in it, so a read walks into the topics at once, and walks past a topic the
655
+ provider no longer lists (`trend_unavailable`) or one with nothing in the category, up
656
+ to five topic pages a read, so a page reaches a topic that carries posts. Any other
657
+ failure marks the source failed until `retryFailed()`.
652
658
 
653
659
  `sdk.discovery.createPersonalFeed({ networks, category?, learning?, recommend? })`
654
660
  builds the same kind of session for a member's For you tab: its Minds slot reads
@@ -658,6 +664,17 @@ and says whether the member is personalized yet (`meta.personalized`). With a
658
664
  `category` (the member's first), the session also reads open-network posts from it;
659
665
  without, it is Minds only. It never carries the radar.
660
666
 
667
+ `sdk.discovery.recommendations({ kind, category?, limit?, session?, exclude? })` is
668
+ the app's one recommender for suggestion rails: `kind` is `member`, `group`, `tag`,
669
+ `network_account` or `story`, and `category` is `all` or a browse category id. It
670
+ suggests only active or warm entities that pass the server's gates (no link feeds,
671
+ bursts, mirrored `name@host` feeds, single-poster groups or unsafe names), leaves out
672
+ what the viewer follows, joined, blocked or muted, and says why a list is empty in
673
+ `meta.reason`. Call `sdk.discovery.recordImpressions({ kind, keys, event })` with
674
+ `shown` when suggestions are on screen and `engaged` on a tap, follow or join: three
675
+ showings without a tap rest a suggestion for fourteen days, and engaging clears it.
676
+ Keep one `session` token per app session so a rail does not reshuffle on every render.
677
+
661
678
  `sdk.posts.categories()` answers the app's categories in the order this member
662
679
  should see them. `meta.order` is `personal`, `popular` or `static`; `meta.reason`
663
680
  says why an answer is not personal (`low_signal`, `deadline`, `requested`,
package/dist/client.d.ts CHANGED
@@ -36,8 +36,12 @@ export declare class HttpClient {
36
36
  post<T>(path: string, body?: unknown, options?: {
37
37
  retry?: boolean;
38
38
  }): Promise<T>;
39
- put<T>(path: string, body?: unknown): Promise<T>;
39
+ put<T>(path: string, body?: unknown, options?: {
40
+ retry?: boolean;
41
+ }): Promise<T>;
40
42
  patch<T>(path: string, body?: unknown): Promise<T>;
41
- delete<T>(path: string): Promise<T>;
43
+ delete<T>(path: string, options?: {
44
+ retry?: boolean;
45
+ }): Promise<T>;
42
46
  fetchRaw(path: string, options: RequestInit): Promise<Response>;
43
47
  }
package/dist/client.js CHANGED
@@ -188,16 +188,17 @@ export class HttpClient {
188
188
  const response = await (options?.retry === false ? this.fetchWithTimeout(url, request) : this.fetchWithRetry(url, request));
189
189
  return this.handleResponse(response);
190
190
  }
191
- async put(path, body) {
191
+ async put(path, body, options) {
192
192
  const url = this.buildUrl(path);
193
- const response = await this.fetchWithRetry(url, {
193
+ const request = {
194
194
  method: 'PUT',
195
195
  headers: {
196
196
  ...this.getAuthHeaders(),
197
197
  'Content-Type': 'application/json',
198
198
  },
199
199
  body: body ? JSON.stringify(body) : undefined,
200
- });
200
+ };
201
+ const response = await (options?.retry === false ? this.fetchWithTimeout(url, request) : this.fetchWithRetry(url, request));
201
202
  return this.handleResponse(response);
202
203
  }
203
204
  async patch(path, body) {
@@ -212,12 +213,10 @@ export class HttpClient {
212
213
  });
213
214
  return this.handleResponse(response);
214
215
  }
215
- async delete(path) {
216
+ async delete(path, options) {
216
217
  const url = this.buildUrl(path);
217
- const response = await this.fetchWithRetry(url, {
218
- method: 'DELETE',
219
- headers: this.getAuthHeaders(),
220
- });
218
+ const request = { method: 'DELETE', headers: this.getAuthHeaders() };
219
+ const response = await (options?.retry === false ? this.fetchWithTimeout(url, request) : this.fetchWithRetry(url, request));
221
220
  return this.handleResponse(response);
222
221
  }
223
222
  async fetchRaw(path, options) {
package/dist/index.d.ts CHANGED
@@ -59,6 +59,8 @@ import { CuratorResource } from './resources/curator.js';
59
59
  import { ReportsResource } from './resources/reports.js';
60
60
  import { ModerationResource } from './resources/moderation.js';
61
61
  import { LinkPreviewResource } from './resources/link-preview.js';
62
+ import { SearchResource } from './resources/search.js';
63
+ import { BookmarksResource } from './resources/bookmarks.js';
62
64
  import { AccountsResource } from './resources/accounts.js';
63
65
  import { RealtimeClient } from './realtime.js';
64
66
  import type { RecursivConfig } from './types.js';
@@ -124,6 +126,10 @@ export declare class Recursiv {
124
126
  readonly reports: ReportsResource;
125
127
  readonly moderation: ModerationResource;
126
128
  readonly linkPreview: LinkPreviewResource;
129
+ /** Blended search: posts, open network, people, groups, topics, stories, related and trending in one answer. */
130
+ readonly search: SearchResource;
131
+ /** Synced bookmarks: the member's saved posts, open-network posts and articles in this app, across devices. */
132
+ readonly bookmarks: BookmarksResource;
127
133
  readonly accounts: AccountsResource;
128
134
  readonly realtime: RealtimeClient;
129
135
  constructor(config?: Partial<RecursivConfig>);
@@ -190,6 +196,10 @@ export type { ReportTargetType, ReportStatus, CreateReportInput, ReportRecord, R
190
196
  export { ModerationResource } from './resources/moderation.js';
191
197
  export type { ModerationTargetType, ModerationActionType, ModerationAppealStatus, ModerationPrinciple, PublicModerationAction, PublicModerationLog, OwnModerationAppeal, OwnModerationAction, OwnModerationLog, ApplyModerationActionInput, } from './resources/moderation.js';
192
198
  export { LinkPreviewResource } from './resources/link-preview.js';
199
+ export { SEARCH_SECTIONS, SearchResource } from './resources/search.js';
200
+ export type { SearchAnswer, SearchGroup, SearchOmitReason, SearchParams, SearchPerson, SearchRelated, SearchResponse, SearchSection, SearchStory, SearchTopic } from './resources/search.js';
201
+ export { BOOKMARK_MERGE_MAX_ITEMS, BookmarksResource } from './resources/bookmarks.js';
202
+ export type { BookmarkedPost, BookmarkInput, BookmarkItem, BookmarkKey, BookmarkKeyRow, BookmarkKind, BookmarkMergeItem, BookmarkMergeResult, BookmarkProtocol, BookmarkRequestOptions, BookmarksPage, SavedBookmark, UnsavedBookmark, } from './resources/bookmarks.js';
193
203
  export { AccountsResource } from './resources/accounts.js';
194
204
  export type { SiblingAccount, SiblingAccountsResponse, SwitchAccountResult, } from './resources/accounts.js';
195
205
  export type { LinkPreview, LinkPreviewResponse, } from './resources/link-preview.js';
@@ -205,7 +215,7 @@ export { BoostsResource } from './resources/boosts.js';
205
215
  export type { BoostBalance, BoostBalanceResponse, BoostCampaign, BoostCampaignResponse, BoostEventResult, BoostLedgerEntry, BoostLedgerKind, BoostPlanCredit, BoostRejectionReason, BoostReviewStatus, BoostStatus, BoostTopupCheckout, BoostTopupConfirmation, BoostTopupLimits, } from './resources/boosts.js';
206
216
  export { ProtocolsResource } from './resources/protocols.js';
207
217
  export { PROTOCOL_ADAPTER_MATURITIES, PROTOCOL_CAPABILITY_OPERATIONS, PROTOCOL_CAPABILITY_STATUSES, } from './resources/protocols.js';
208
- export type { SocialProfile, SocialSubscription, SocialSubscriptionChange, SocialProfilePage, SocialRadar, SocialRadarCluster, SocialRadarMember, SocialRadarParams, SocialRadarPosts, SocialRadarStory, SocialNetwork, SocialPost, SocialConversation, SocialTrend, SocialTrendProvenance, SocialTrends, SocialTrendSourceIndex, SocialTrendFeed, SocialPopularProfiles, SocialAuthorFeed, SocialFollowingFeed, SocialFollowingSource, SearchPublicProfilesParams, SearchPublicPostsParams, SocialPostSearch, HostedProtocolConnections, HostedProtocol, HostedProtocolIdentity, HostedNostrChallenge, HostedNostrIdentityProof, NativeNostrPrepareInput, NativeNostrPrepared, NativeNostrAccount, NativeNostrPostState, NativeNostrReceipt, NativeSocialProtocol, FederationIdentity, FederationIdentityView, NativeSocialAccount, NativeSocialCallbackInput, NativeReplyPrepareInput, NativePostPrepareInput, NativeMediaInput, NativeMediaItem, NativePostBatch, NativePostHistory, NativeDelegatedPostHistoryItem, NativeNostrPostHistoryItem, NativeFollowSourceInput, NativeFollowPrepareInput, NativeFollowTarget, NativeFollowReceipt, NativeFollowState, NativeFollowAccount, NativeFollowStates, NativeFollowReview, NativeLikeSourceInput, NativeRepostSourceInput, NativeLikePrepareInput, NativeRepostPrepareInput, NativeLikeTarget, NativeRepostTarget, NativeLikeReceipt, NativeRepostReceipt, NativeLikeState, NativeRepostState, NativeLikeReview, NativeRepostReview, NativeReplyReceipt, NativeReplyBatch, NativeReplyHistory, NativeDelegatedReplyHistoryItem, NativeNostrReplyResult, NativeNostrReplyHistoryItem, NativeReplyRemovalReceipt, NativeReplyRemovalPreview, SocialSourceRef, SocialSourceControl, SocialSourceControlDisplay, SocialSourceControlChange, HostedPostPreview, ConnectHostedIdentityInput, CreateHostedIdentityInput, CreatedHostedProtocolIdentity, PreviewHostedPostInput, PublishHostedPostInput, ListProtocolCandidatesParams, ProtocolAdapterMaturity, ProtocolAdapterMetadata, ProtocolCandidateListResponse, ProtocolCandidatePageMeta, ProtocolCapabilityOperation, ProtocolCapabilityStatus, ProtocolCollectionStatus, ProtocolIngestionReplayResult, ProtocolIngestionStatus, ProtocolQuarantinedCandidate, ProtocolRefreshResult, ProtocolSettings, ProtocolSettingsUpdateResult, ReplayProtocolIngestionJobInput, UpdateSettingsInput as UpdateProtocolSettingsInput, } from './resources/protocols.js';
218
+ export type { SocialProfile, SocialSubscription, SocialSubscriptionChange, SocialProfilePage, SocialRadar, SocialRadarCluster, SocialRadarMember, SocialRadarParams, SocialRadarPosts, SocialRadarStory, SocialRadarStoryArchive, SocialNetwork, SocialPost, SocialConversation, SocialTrend, SocialTrendProvenance, SocialTrends, SocialTrendSourceIndex, SocialTrendFeed, SocialPopularProfiles, SocialAuthorFeed, SocialFollowingFeed, SocialFollowingSource, SearchPublicProfilesParams, SearchPublicPostsParams, SocialPostSearch, HostedProtocolConnections, HostedProtocol, HostedProtocolIdentity, HostedNostrChallenge, HostedNostrIdentityProof, NativeNostrPrepareInput, NativeNostrPrepared, NativeNostrAccount, NativeNostrPostState, NativeNostrReceipt, NativeSocialProtocol, FederationIdentity, FederationIdentityView, NativeSocialAccount, NativeSocialCallbackInput, NativeReplyPrepareInput, NativePostPrepareInput, NativeMediaInput, NativeMediaItem, NativePostBatch, NativePostHistory, NativeDelegatedPostHistoryItem, NativeNostrPostHistoryItem, NativeFollowSourceInput, NativeFollowPrepareInput, NativeFollowTarget, NativeFollowReceipt, NativeFollowState, NativeFollowAccount, NativeFollowStates, NativeFollowReview, NativeLikeSourceInput, NativeRepostSourceInput, NativeLikePrepareInput, NativeRepostPrepareInput, NativeLikeTarget, NativeRepostTarget, NativeLikeReceipt, NativeRepostReceipt, NativeLikeState, NativeRepostState, NativeLikeReview, NativeRepostReview, NativeReplyReceipt, NativeReplyBatch, NativeReplyHistory, NativeDelegatedReplyHistoryItem, NativeNostrReplyResult, NativeNostrReplyHistoryItem, NativeReplyRemovalReceipt, NativeReplyRemovalPreview, SocialSourceRef, SocialSourceControl, SocialSourceControlDisplay, SocialSourceControlChange, HostedPostPreview, ConnectHostedIdentityInput, CreateHostedIdentityInput, CreatedHostedProtocolIdentity, PreviewHostedPostInput, PublishHostedPostInput, ListProtocolCandidatesParams, ProtocolAdapterMaturity, ProtocolAdapterMetadata, ProtocolCandidateListResponse, ProtocolCandidatePageMeta, ProtocolCapabilityOperation, ProtocolCapabilityStatus, ProtocolCollectionStatus, ProtocolIngestionReplayResult, ProtocolIngestionStatus, ProtocolQuarantinedCandidate, ProtocolRefreshResult, ProtocolSettings, ProtocolSettingsUpdateResult, ReplayProtocolIngestionJobInput, UpdateSettingsInput as UpdateProtocolSettingsInput, } from './resources/protocols.js';
209
219
  export { InboxResource } from './resources/inbox.js';
210
220
  export { NetworkResource } from './resources/network.js';
211
221
  export { SimulatorResource } from './resources/simulator.js';
package/dist/index.js CHANGED
@@ -60,6 +60,8 @@ import { CuratorResource } from './resources/curator.js';
60
60
  import { ReportsResource } from './resources/reports.js';
61
61
  import { ModerationResource } from './resources/moderation.js';
62
62
  import { LinkPreviewResource } from './resources/link-preview.js';
63
+ import { SearchResource } from './resources/search.js';
64
+ import { BookmarksResource } from './resources/bookmarks.js';
63
65
  import { AccountsResource } from './resources/accounts.js';
64
66
  import { RealtimeClient } from './realtime.js';
65
67
  export class Recursiv {
@@ -125,6 +127,10 @@ export class Recursiv {
125
127
  reports;
126
128
  moderation;
127
129
  linkPreview;
130
+ /** Blended search: posts, open network, people, groups, topics, stories, related and trending in one answer. */
131
+ search;
132
+ /** Synced bookmarks: the member's saved posts, open-network posts and articles in this app, across devices. */
133
+ bookmarks;
128
134
  accounts;
129
135
  realtime;
130
136
  constructor(config) {
@@ -181,13 +187,15 @@ export class Recursiv {
181
187
  this.webhooks = new WebhooksResource(client);
182
188
  this.jobs = new JobsResource(client);
183
189
  this.curator = new CuratorResource(client);
184
- this.discovery = new DiscoveryResource(this.posts, this.protocols, this.curator);
190
+ this.discovery = new DiscoveryResource(this.posts, this.protocols, this.curator, client);
185
191
  this.video = new VideoResource(client);
186
192
  this.appSubscriptions = new AppSubscriptionsResource(client);
187
193
  this.boosts = new BoostsResource(client);
188
194
  this.reports = new ReportsResource(client);
189
195
  this.moderation = new ModerationResource(client);
190
196
  this.linkPreview = new LinkPreviewResource(client);
197
+ this.search = new SearchResource(client);
198
+ this.bookmarks = new BookmarksResource(client);
191
199
  this.accounts = new AccountsResource(client);
192
200
  this.realtime = new RealtimeClient(client);
193
201
  this.#http = client;
@@ -243,6 +251,8 @@ export { AppSubscriptionsResource } from './resources/app-subscriptions.js';
243
251
  export { ReportsResource } from './resources/reports.js';
244
252
  export { ModerationResource } from './resources/moderation.js';
245
253
  export { LinkPreviewResource } from './resources/link-preview.js';
254
+ export { SEARCH_SECTIONS, SearchResource } from './resources/search.js';
255
+ export { BOOKMARK_MERGE_MAX_ITEMS, BookmarksResource } from './resources/bookmarks.js';
246
256
  export { AccountsResource } from './resources/accounts.js';
247
257
  export { OrganizationSettingsResource } from './resources/organization-settings.js';
248
258
  export { ProjectSettingsResource } from './resources/project-settings.js';
@@ -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
+ }
@@ -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);
@@ -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;
@@ -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,9 +1,71 @@
1
+ import type { HttpClient } from '../client.js';
1
2
  import type { DiscoveryLayout, Post } from '../types.js';
2
3
  import type { CuratorResource } from './curator.js';
3
4
  import type { DiscoveryLearning } from './discovery-learning.js';
4
5
  import type { PostsResource } from './posts.js';
5
6
  import type { ProtocolsResource, SocialNetwork, SocialPost, SocialRadar, SocialRadarPosts } from './protocols.js';
6
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
+ }
7
69
  export interface DiscoveryGroupCandidate {
8
70
  id: string;
9
71
  name: string;
@@ -162,7 +224,7 @@ export interface DiscoveryFeedPage {
162
224
  export declare const GALLERY_MAX_ITEMS = 12;
163
225
  /** Per-viewer feed session. Create a new session when user, app, category or
164
226
  * source changes. All reads retain existing server authorization/moderation.
165
- * 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. */
166
228
  export declare class DiscoveryFeedSession {
167
229
  private posts;
168
230
  private protocols;
@@ -187,6 +249,15 @@ export declare class DiscoveryFeedSession {
187
249
  retryFailed(): void;
188
250
  next(): Promise<DiscoveryFeedPage>;
189
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;
190
261
  private readPage;
191
262
  /** Page 0: the top breaking cluster, else the top cluster. Every second later page: the next unused story or viral cluster. */
192
263
  private radarCluster;
@@ -203,7 +274,24 @@ export declare class DiscoveryResource {
203
274
  private posts;
204
275
  private protocols;
205
276
  private curator?;
206
- constructor(posts: PostsResource, protocols: ProtocolsResource, curator?: Pick<CuratorResource, "forYou"> | undefined);
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
+ }>;
207
295
  /**
208
296
  * A For you Discover feed: the member's For You ranking (asked for Discover's surface, with layouts and a
209
297
  * snapshot cursor) in a session's Minds slot. With a category, the session also reads open-network posts