@recursiv/sdk 0.7.29 → 0.7.30

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
@@ -75,6 +75,7 @@ for await (const chunk of r.agents.chatStream(agent.id, { message: 'Hello!' }))
75
75
  | Resource | Key methods |
76
76
  |----------|------------|
77
77
  | `r.agents` | `create`, `update`, `chat`, `chatStream`, `grantProjectAccess`, `conversations` |
78
+ | `r.e2ee` | `registerDevice`, `devices`, `revokeDevice`, `publishKeyPackages`, `claimKeyPackage`, `appendEvent`, `events`, `acknowledge` (flag-gated E2EE v2 transport) |
78
79
  | `r.brain` | `sendMessage` |
79
80
  | `r.commands` | `execute`, `gatePrompt` |
80
81
  | `r.projectBrain` | `tasks`, `decisions`, `milestones`, `completeTask`, `usage` |
@@ -110,7 +111,7 @@ for await (const chunk of r.agents.chatStream(agent.id, { message: 'Hello!' }))
110
111
  | `r.admin` | User management, stats, content reports (`listReports`, `resolveReport`, `dismissReport`) |
111
112
  | `r.dispatcher` | Task queue, claims, signals, outcomes, webhooks |
112
113
  | `r.goals` | Org goals with task rollups (group dispatcher tasks) |
113
- | `r.protocols` | Private Minds safety: `sourceControls` (current enablement and saved choices), `setSourceControl`, `onSourceControlChange`, `reportSource`. Delegated accounts: `nativeSocialAccounts`, `startNativeSocialConnection`, `finishNativeSocialConnection`, `disconnectNativeSocialAccount`. Reviewed replies and optional linked posts: `prepareNativeReply`, `submitNativeReply`, `nativeReplyStatus`, `nativeReplyHistory`, `prepareNativeReplyRemoval`, `removeNativeReply` (require deployed API support and app enablement). Public reading: `publicNetworks`, `searchPublicProfiles`, `getPublicProfile`, `getPublicAuthorFeed`, `getFollowingFeed`, `getPublicConversation`, `getPublicTrends`, `getPublicTrendFeed`; 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` |
114
+ | `r.protocols` | Private Minds safety: `sourceControls` (current enablement and saved choices), `setSourceControl`, `onSourceControlChange`, `reportSource`. Delegated accounts: `nativeSocialAccounts`, `startNativeSocialConnection`, `finishNativeSocialConnection`, `disconnectNativeSocialAccount`. 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: `publicNetworks`, `searchPublicProfiles`, `getPublicProfile`, `getPublicAuthorFeed`, `getFollowingFeed`, `getPublicConversation`, `getPublicTrends`, `getPublicTrendFeed`; 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` |
114
115
 
115
116
  Full reference: see [llm.md](./llm.md) (ships with this package) or the type definitions in `dist/`.
116
117
 
@@ -570,3 +571,21 @@ Keep the cursor opaque and discard it when the app or signed-in user changes.
570
571
  History order is fixed by creation time, so later delivery updates do not move
571
572
  older entries across page boundaries. Reopen each result for its current saved
572
573
  state and obtain a fresh review before a new write.
574
+
575
+ ### Public protocol post links
576
+
577
+ Visitors can read a public conversation in an explicitly configured app without signing in:
578
+
579
+ ```ts
580
+ const visitor = new Recursiv({ anonymous: true });
581
+ const { data: networks } = await visitor.protocols.sharedNetworks(projectId);
582
+ const { data: conversation } = await visitor.protocols.getSharedConversation(projectId, {
583
+ protocol: 'nostr', post: publicEventId,
584
+ });
585
+ ```
586
+
587
+ These reads apply app moderation, provider public-visibility/deletion checks and bounded
588
+ per-IP/app budgets. They return no subscriptions, personal safety preferences, account
589
+ connections or signing authority. Missing configuration or unavailable safety checks fail
590
+ closed. Signed-in members continue using `getPublicConversation` to apply personal
591
+ blocks/mutes as well. All reply and account endpoints still require authentication.
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ import { PostsResource } from './resources/posts.js';
3
3
  import { UsersResource } from './resources/users.js';
4
4
  import { CommunitiesResource } from './resources/communities.js';
5
5
  import { ChatResource } from './resources/chat.js';
6
+ import { E2eeResource } from './resources/e2ee.js';
6
7
  import { ProjectsResource } from './resources/projects.js';
7
8
  import { AgentsResource } from './resources/agents.js';
8
9
  import { EvidenceResource } from './resources/evidence.js';
@@ -64,6 +65,8 @@ export declare class Recursiv {
64
65
  readonly users: UsersResource;
65
66
  readonly communities: CommunitiesResource;
66
67
  readonly chat: ChatResource;
68
+ /** E2EE v2 ciphertext transport (flag-gated). */
69
+ readonly e2ee: E2eeResource;
67
70
  readonly projects: ProjectsResource;
68
71
  readonly agents: AgentsResource;
69
72
  readonly evidence: EvidenceResource;
@@ -136,6 +139,8 @@ export { PostsResource } from './resources/posts.js';
136
139
  export { UsersResource } from './resources/users.js';
137
140
  export { CommunitiesResource } from './resources/communities.js';
138
141
  export { ChatResource } from './resources/chat.js';
142
+ export { E2eeResource } from './resources/e2ee.js';
143
+ export type { E2eeDevice, E2eeEnvelope, E2eeEvent } from './resources/e2ee.js';
139
144
  export { ProjectsResource } from './resources/projects.js';
140
145
  export { AgentsResource, SUPPORTED_MODELS } from './resources/agents.js';
141
146
  export { EvidenceResource } from './resources/evidence.js';
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ import { PostsResource } from './resources/posts.js';
4
4
  import { UsersResource } from './resources/users.js';
5
5
  import { CommunitiesResource } from './resources/communities.js';
6
6
  import { ChatResource } from './resources/chat.js';
7
+ import { E2eeResource } from './resources/e2ee.js';
7
8
  import { ProjectsResource } from './resources/projects.js';
8
9
  import { AgentsResource } from './resources/agents.js';
9
10
  import { EvidenceResource } from './resources/evidence.js';
@@ -65,6 +66,8 @@ export class Recursiv {
65
66
  users;
66
67
  communities;
67
68
  chat;
69
+ /** E2EE v2 ciphertext transport (flag-gated). */
70
+ e2ee;
68
71
  projects;
69
72
  agents;
70
73
  evidence;
@@ -125,6 +128,7 @@ export class Recursiv {
125
128
  this.users = new UsersResource(client);
126
129
  this.communities = new CommunitiesResource(client);
127
130
  this.chat = new ChatResource(client);
131
+ this.e2ee = new E2eeResource(client);
128
132
  this.projects = new ProjectsResource(client);
129
133
  this.agents = new AgentsResource(client);
130
134
  this.evidence = new EvidenceResource(client);
@@ -200,6 +204,7 @@ export { PostsResource } from './resources/posts.js';
200
204
  export { UsersResource } from './resources/users.js';
201
205
  export { CommunitiesResource } from './resources/communities.js';
202
206
  export { ChatResource } from './resources/chat.js';
207
+ export { E2eeResource } from './resources/e2ee.js';
203
208
  export { ProjectsResource } from './resources/projects.js';
204
209
  export { AgentsResource, SUPPORTED_MODELS } from './resources/agents.js';
205
210
  export { EvidenceResource } from './resources/evidence.js';
@@ -0,0 +1,93 @@
1
+ import type { HttpClient } from '../client.js';
2
+ import type { SingleResponse } from '../types.js';
3
+ /** Public device record from the E2EE v2 directory. Never contains private keys. */
4
+ export interface E2eeDevice {
5
+ id: string;
6
+ userId: string;
7
+ deviceName: string;
8
+ realm: string;
9
+ signingKey: string;
10
+ credential: unknown;
11
+ isRoot: boolean;
12
+ status: 'active' | 'revoked' | string;
13
+ revokedAt: string | null;
14
+ }
15
+ /** Opaque `recursiv-mls-v2-draft1` envelope with base64 MLS bytes. */
16
+ export interface E2eeEnvelope {
17
+ profile: 'recursiv-mls-v2-draft1';
18
+ realm: string;
19
+ conversation: string;
20
+ event_id: string;
21
+ sender_device: string;
22
+ kind: 'application' | 'commit';
23
+ mls: string;
24
+ }
25
+ export interface E2eeEvent {
26
+ seq: number;
27
+ kind: string;
28
+ envelope: E2eeEnvelope;
29
+ /** Present only for the device the Welcome was issued to. */
30
+ welcome: string | null;
31
+ createdAt: string;
32
+ }
33
+ /**
34
+ * E2EE v2 ciphertext transport. The server relays envelopes and public
35
+ * credentials only; encryption happens in the shared core on each device.
36
+ * Requires the `e2eeV2` network feature flag.
37
+ */
38
+ export declare class E2eeResource {
39
+ private client;
40
+ constructor(client: HttpClient);
41
+ registerDevice(input: {
42
+ device_name: string;
43
+ realm: string;
44
+ signing_key: string;
45
+ credential: Record<string, unknown>;
46
+ }): Promise<SingleResponse<E2eeDevice>>;
47
+ devices(params?: {
48
+ user_id?: string;
49
+ }): Promise<{
50
+ data: E2eeDevice[];
51
+ }>;
52
+ revokeDevice(deviceId: string): Promise<SingleResponse<E2eeDevice>>;
53
+ publishKeyPackages(input: {
54
+ device_id: string;
55
+ packages: string[];
56
+ }): Promise<SingleResponse<{
57
+ published: number;
58
+ }>>;
59
+ claimKeyPackage(input: {
60
+ device_id: string;
61
+ target_device_id: string;
62
+ }): Promise<SingleResponse<{
63
+ keyPackageId: string;
64
+ bytes: string;
65
+ device: E2eeDevice;
66
+ }>>;
67
+ appendEvent(conversationId: string, input: {
68
+ device_id: string;
69
+ envelope: E2eeEnvelope;
70
+ welcome?: string;
71
+ welcome_for_device_id?: string;
72
+ expected_commit_seq?: number;
73
+ }): Promise<SingleResponse<{
74
+ seq: number;
75
+ duplicate: boolean;
76
+ commitSeq: number;
77
+ }>>;
78
+ events(conversationId: string, params: {
79
+ device_id: string;
80
+ after_seq?: number;
81
+ limit?: number;
82
+ }): Promise<SingleResponse<{
83
+ headSeq: number;
84
+ commitSeq: number;
85
+ events: E2eeEvent[];
86
+ }>>;
87
+ acknowledge(conversationId: string, input: {
88
+ device_id: string;
89
+ seq: number;
90
+ }): Promise<SingleResponse<{
91
+ ackedSeq: number;
92
+ }>>;
93
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * E2EE v2 ciphertext transport. The server relays envelopes and public
3
+ * credentials only; encryption happens in the shared core on each device.
4
+ * Requires the `e2eeV2` network feature flag.
5
+ */
6
+ export class E2eeResource {
7
+ client;
8
+ constructor(client) {
9
+ this.client = client;
10
+ }
11
+ registerDevice(input) {
12
+ return this.client.post('/e2ee/devices', input);
13
+ }
14
+ devices(params) {
15
+ return this.client.get('/e2ee/devices', params);
16
+ }
17
+ revokeDevice(deviceId) {
18
+ return this.client.post(`/e2ee/devices/${encodeURIComponent(deviceId)}/revoke`);
19
+ }
20
+ publishKeyPackages(input) {
21
+ return this.client.post('/e2ee/key-packages', input);
22
+ }
23
+ claimKeyPackage(input) {
24
+ return this.client.post('/e2ee/key-packages/claim', input);
25
+ }
26
+ appendEvent(conversationId, input) {
27
+ // Retries are safe: the server is idempotent on (conversation, device, event id).
28
+ return this.client.post(`/e2ee/conversations/${encodeURIComponent(conversationId)}/events`, input, { retry: true });
29
+ }
30
+ events(conversationId, params) {
31
+ return this.client.get(`/e2ee/conversations/${encodeURIComponent(conversationId)}/events`, params);
32
+ }
33
+ acknowledge(conversationId, input) {
34
+ return this.client.post(`/e2ee/conversations/${encodeURIComponent(conversationId)}/ack`, input);
35
+ }
36
+ }
@@ -703,6 +703,14 @@ export declare class ProtocolsResource {
703
703
  private subscriptionChanged;
704
704
  /** Confirmed safety choices from this authenticated SDK instance. Unsubscribe when its account or view changes. */
705
705
  onSourceControlChange(listener: (change: SocialSourceControlChange) => void): () => void;
706
+ /** Public-only conversation catalog for a configured app. Works with an anonymous client. */
707
+ sharedNetworks(projectId: string): Promise<SingleResponse<SocialNetwork[]>>;
708
+ /** Keyless public post read with app moderation; no account state or connected write authority. */
709
+ getSharedConversation(projectId: string, params: {
710
+ protocol: string;
711
+ post: string;
712
+ readRef?: string;
713
+ }): Promise<SingleResponse<SocialConversation>>;
706
714
  publicNetworks(): Promise<SingleResponse<SocialNetwork[]>>;
707
715
  /** Query the live public network. It does not import users or posts. */
708
716
  searchPublicProfiles(params: SearchPublicProfilesParams): Promise<SingleResponse<SocialProfilePage>>;
@@ -60,6 +60,16 @@ export class ProtocolsResource {
60
60
  this.sourceControlListeners.add(listener);
61
61
  return () => { this.sourceControlListeners.delete(listener); };
62
62
  }
63
+ /** Public-only conversation catalog for a configured app. Works with an anonymous client. */
64
+ sharedNetworks(projectId) {
65
+ return this.client.get(`/protocols/public/${encodeURIComponent(projectId)}/networks`);
66
+ }
67
+ /** Keyless public post read with app moderation; no account state or connected write authority. */
68
+ getSharedConversation(projectId, params) {
69
+ return this.client.get(`/protocols/public/${encodeURIComponent(projectId)}/conversation`, {
70
+ protocol: params.protocol, post: params.post, readRef: params.readRef,
71
+ });
72
+ }
63
73
  publicNetworks() {
64
74
  return this.client.get('/protocols/social/networks');
65
75
  }
package/llm.md CHANGED
@@ -476,6 +476,19 @@ const { data: msgs } = await r.chat.messages(dm.id, { limit: 50 })
476
476
 
477
477
  Methods: `conversations`, `communityConversation`, `conversation`, `messages`, `send`, `dm`, `createGroup`, `deleteConversation`, `reactToMessage`, `editMessage`, `deleteMessage`, `markAsRead`, `unreadCount`
478
478
 
479
+ ### r.e2ee — End-to-end encryption transport (flag-gated)
480
+
481
+ Relays opaque MLS envelopes, public device credentials and one-time key packages for the shared E2EE core; the server never sees keys or plaintext. Requires the `e2eeV2` network feature flag; otherwise every call returns `feature_disabled`.
482
+
483
+ ```ts
484
+ const { data: device } = await r.e2ee.registerDevice({ device_name: 'phone-1', realm: 'minds', signing_key, credential });
485
+ await r.e2ee.publishKeyPackages({ device_id: device.id, packages: [keyPackageBase64] });
486
+ const { data: claimed } = await r.e2ee.claimKeyPackage({ device_id: device.id, target_device_id: peerDeviceId });
487
+ await r.e2ee.appendEvent(conversationId, { device_id: device.id, envelope }); // idempotent per event_id
488
+ const { data: page } = await r.e2ee.events(conversationId, { device_id: device.id, after_seq: 0 });
489
+ await r.e2ee.acknowledge(conversationId, { device_id: device.id, seq: page.headSeq });
490
+ ```
491
+
479
492
  ### r.realtime — WebSocket Chat Client
480
493
 
481
494
  Provides persistent real-time streaming and chat presence, useful for building frontends (e.g. React Native, Web).
@@ -620,7 +633,7 @@ if (subscription.active) {
620
633
  - `r.inviteCodes` / `r.inviteCodesAdmin` — Invite code management
621
634
  - `r.organizationSettings` / `r.organizationSecurity` — Org configuration
622
635
  - Delegated social accounts: `r.protocols.nativeSocialAccounts()`, `startNativeSocialConnection({ protocol, identifier })`, `finishNativeSocialConnection({ protocol, state, code?, iss?, error? })`, `disconnectNativeSocialAccount(id)`. Example: `const { data } = await r.protocols.startNativeSocialConnection({ protocol: "bluesky", identifier: "alice.bsky.social" })`; open `data.authorizationUrl`, then complete with the same signed-in app user. Never log callback codes, send passwords, or auto-retry OAuth callbacks. Shared replies (off until schema/API deployment and live verification): `prepareNativeReply({ clientMutationId, text, parent: { protocol, nativeId, readRef? }, accountIds, linkedPostAccountIds?, recoverFrom?: { batchId, deliveryIds } })`, `submitNativeReply(id, { approve: true, deliveryIds })`, `nativeReplyStatus(id)`, `nativeReplyHistory({ limit?, cursor? })`, `prepareNativeReplyRemoval(id, { deliveryIds })`, `removeNativeReply(id, { approve: true, removals })`. Review account, destination, `kind`, source and exact `wireText`; connection is not publication approval. `linkedPostAccountIds` explicitly selects different-network standalone posts with a source link, never matching threaded replies or a same-network denial fallback. Preserve mutation IDs, show per-destination results and reconcile uncertain attempts without reposting. To review selected unsent or definitively rejected deliveries again, use recoverFrom with a new mutation ID, the same source/accounts and freshly reviewed text; preserve that ID on uncertain preparation. Only saved recoverable destinations qualify, current gates still apply, and replacedBy disables old sends atomically. Never restart unknown, accepted or published deliveries. The new preview still needs explicit approval. Saved delegated drafts/results: `const { data: history } = await r.protocols.nativeReplyHistory({ limit: 20 })`; use its opaque cursor for the next page and `nativeReplyStatus(item.id)` for details. This is a private read, not a write or provider refresh, and includes Nostr results marked type=nostr. Use nativeNostrReplyResult(item.id) to check that recorded event on its relay without signing or resending. Discard page state on owner/app changes. See README for examples and the callback, revocation and removal contracts.
623
- - `r.protocols` — Private app safety: `sourceControls` returns current `enabled` plus paged saved choices; `setSourceControl`, `onSourceControlChange` and `reportSource` share app scope. Example: `const choices = new Map(); const stop = r.protocols.onSourceControlChange(change => choices.set(JSON.stringify([change.source, change.control]), change.enabled));` then `await r.protocols.setSourceControl({ source: { protocol: "bluesky", kind: "actor", nativeId: "did:plc:aaaaaaaaaaaaaaaaaaaaaaaa" }, control: "mute", enabled: true, scope: "minds" });` and call `stop(); choices.clear()` on account change/unmount. Notifications follow an exact server confirmation, never an uncertain response. These preferences and reports apply in Minds, not on the remote network. Public reading: `publicNetworks`, `searchPublicProfiles`, `getPublicProfile`, `getPublicAuthorFeed`, `getFollowingFeed`, `getPublicConversation`, `getPublicTrends`, `getPublicTrendFeed`; private app subscriptions: `listSubscriptions`, `subscribeToProfile`, `unsubscribeFromProfile`, `onSubscriptionChange`. Example after selecting a returned source post: `const { data: thread } = await r.protocols.getPublicConversation({ protocol: post.protocol, post: post.nativeId, readRef: post.readRef })`. Render `focus`, oldest-first `ancestors` and direct `replies`; `public_provider_window` never promises every reply, and `limited` marks known omissions. Requires both `users:read` and `posts:read` on the current human app key. Protocol discovery/settings: `const { data: adapters } = await r.protocols.list()` returns bounded `{ id, name, icon, color, maturity, capabilities, limitations }` metadata; `supported` means implemented, not configured, healthy, authorized, or enabled. Methods: `list`, `status`, `listCandidates`, `getCandidate`, `search`, `getSettings`, `refresh`, `updateSettings`.
636
+ - `r.protocols` — Private app safety: `sourceControls` returns current `enabled` plus paged saved choices; `setSourceControl`, `onSourceControlChange` and `reportSource` share app scope. Example: `const choices = new Map(); const stop = r.protocols.onSourceControlChange(change => choices.set(JSON.stringify([change.source, change.control]), change.enabled));` then `await r.protocols.setSourceControl({ source: { protocol: "bluesky", kind: "actor", nativeId: "did:plc:aaaaaaaaaaaaaaaaaaaaaaaa" }, control: "mute", enabled: true, scope: "minds" });` and call `stop(); choices.clear()` on account change/unmount. Notifications follow an exact server confirmation, never an uncertain response. These preferences and reports apply in Minds, not on the remote network. Keyless post links: `const visitor = new Recursiv({ anonymous: true }); const { data: thread } = await visitor.protocols.getSharedConversation(projectId, { protocol: 'nostr', post: publicEventId })`; `sharedNetworks(projectId)` lists public conversation capabilities. Only configured apps; app moderation and rate limits apply, no member state or write authority. Member public reading: `publicNetworks`, `searchPublicProfiles`, `getPublicProfile`, `getPublicAuthorFeed`, `getFollowingFeed`, `getPublicConversation`, `getPublicTrends`, `getPublicTrendFeed`; private app subscriptions: `listSubscriptions`, `subscribeToProfile`, `unsubscribeFromProfile`, `onSubscriptionChange`. Example after selecting a returned source post: `const { data: thread } = await r.protocols.getPublicConversation({ protocol: post.protocol, post: post.nativeId, readRef: post.readRef })`. Render `focus`, oldest-first `ancestors` and direct `replies`; `public_provider_window` never promises every reply, and `limited` marks known omissions. Requires both `users:read` and `posts:read` on the current human app key. Protocol discovery/settings: `const { data: adapters } = await r.protocols.list()` returns bounded `{ id, name, icon, color, maturity, capabilities, limitations }` metadata; `supported` means implemented, not configured, healthy, authorized, or enabled. Methods: `list`, `status`, `listCandidates`, `getCandidate`, `search`, `getSettings`, `refresh`, `updateSettings`.
624
637
  - Native Nostr: `const { data: preview } = await r.protocols.prepareNativeNostr(identityId, { action: "publish", content: draft })`; review and sign the exact public event, then `submitNativeNostr(identityId, { intent: preview.intent, event, approve: true })`. Preparation/submission never auto-retry. After uncertainty, use `getNativeNostrPost(identityId, preview.postId ?? preview.eventId)` before a user-selected retry of the identical intent/event: deletion checks the original note's `postId`, publication checks `eventId`. Check `nativeNostrAccount()` enablement first; keys stay with the signer. For replies, after `nativeNostrAccount()` advertises `reply_text_note`, use `prepareNativeNostr(identityId, { action: "reply", content: draft, parentId: post.nativeId })`. Review returned parent/root/participants/relay and exact event, sign with the connected client signer, then `submitNativeNostr(identityId, { intent, event, approve: true })`. Requires users:read, posts:read and posts:write. Kind1 public notes only; reply enablement and shared UI/live proof remain pending. Do not inherit arbitrary notification tags or relay hints. Signing mutations never auto-retry; check `getNativeNostrPost(identityId, eventId)` after uncertainty before a user-selected identical-event retry.
625
638
  - Public trends: require `public_trends` from `publicNetworks()`, then `getPublicTrends({ protocol, limit?, cursor? })`; render `kind: topics` as source topics and `kind: posts` as source posts. With `trend_feed`, open only a returned current topic using `getPublicTrendFeed({ protocol, topic: trend.nativeId, limit?, cursor? })`. Show provider, observedAt and provider_ranked_window coverage; null rankingWindow is unknown, not a daily/global ranking. Preserve source order, canonical IDs and readRef, and opaque cursors. Topic catalogs do not paginate; Mastodon trends stop at 200 source positions. Both methods require users:read plus posts:read on the current human app key; Nostr trends are unsupported. Propagate errors; trend_unavailable means refresh the topic list. No ingestion or native writes.
626
639
  - `listCandidates({ protocol?, limit?, cursor? })` and `getCandidate(id)` are read-only quarantine review, not published-post search. Both require `admin` scope, a project-bound key, a live admin role, and existing project-admin access. The key supplies app/network scope; neither method accepts another project or network. They never invoke refresh, change settings, or publish.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@recursiv/sdk",
3
- "version": "0.7.29",
3
+ "version": "0.7.30",
4
4
  "description": "TypeScript SDK for the Recursiv API",
5
5
  "license": "FSL-1.1-ALv2",
6
6
  "type": "module",