@recursiv/sdk 0.7.23 → 0.7.26
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 +37 -1
- package/dist/client.d.ts +3 -1
- package/dist/client.js +4 -3
- package/dist/index.d.ts +1 -1
- package/dist/resources/protocols.d.ts +108 -1
- package/dist/resources/protocols.js +33 -0
- package/llm.md +3 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -110,7 +110,7 @@ for await (const chunk of r.agents.chatStream(agent.id, { message: 'Hello!' }))
|
|
|
110
110
|
| `r.admin` | User management, stats, content reports (`listReports`, `resolveReport`, `dismissReport`) |
|
|
111
111
|
| `r.dispatcher` | Task queue, claims, signals, outcomes, webhooks |
|
|
112
112
|
| `r.goals` | Org goals with task rollups (group dispatcher tasks) |
|
|
113
|
-
| `r.protocols` | Public reading: `publicNetworks`, `searchPublicProfiles`, `getPublicProfile`, `getPublicAuthorFeed`, `getFollowingFeed`, `getPublicConversation`; 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` |
|
|
113
|
+
| `r.protocols` | 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
114
|
|
|
115
115
|
Full reference: see [llm.md](./llm.md) (ships with this package) or the type definitions in `dist/`.
|
|
116
116
|
|
|
@@ -423,3 +423,39 @@ import type {
|
|
|
423
423
|
## License
|
|
424
424
|
|
|
425
425
|
FSL-1.1-ALv2 — source-available, converts to Apache 2.0 after 2 years.
|
|
426
|
+
|
|
427
|
+
### Public source trends
|
|
428
|
+
|
|
429
|
+
Check `publicNetworks()` for `public_trends` before calling
|
|
430
|
+
`getPublicTrends({ protocol, limit: 10, cursor })`. The response is a discriminated
|
|
431
|
+
union: `kind: 'topics'` contains Bluesky topics; `kind: 'posts'` contains public
|
|
432
|
+
ActivityPub posts ranked by mastodon.social. Preserve provider order and display
|
|
433
|
+
`provider`, `observedAt`, and `coverage: 'provider_ranked_window'`. A null
|
|
434
|
+
`rankingWindow` means the provider has not supplied an exact ranking interval.
|
|
435
|
+
|
|
436
|
+
For a network with `trend_feed`, open a returned topic with
|
|
437
|
+
`getPublicTrendFeed({ protocol, topic: topic.nativeId, limit: 10, cursor })`.
|
|
438
|
+
The service rechecks current topic availability before each page; a disappeared
|
|
439
|
+
topic returns `trend_unavailable` rather than an empty feed. Keep canonical post
|
|
440
|
+
IDs and provider `readRef` when opening `getPublicConversation`. Cursors are
|
|
441
|
+
opaque; topic catalogs have no pagination and the Mastodon post window stops at
|
|
442
|
+
200 source positions. Both reads require `users:read` and `posts:read` on the
|
|
443
|
+
current human project-bound app key. Nostr does not advertise trends. Provider
|
|
444
|
+
failures propagate as SDK errors, and neither method ingests or publishes posts.
|
|
445
|
+
|
|
446
|
+
### Native Nostr signing
|
|
447
|
+
|
|
448
|
+
Use `protocols.nativeNostrAccount()` to check the app's explicit native-write
|
|
449
|
+
capability. For an enabled connected key, prepare a text post with
|
|
450
|
+
`prepareNativeNostr(identityId, { action: 'publish', content })`, show its
|
|
451
|
+
identity, relay and text, and ask the client signer to sign the returned event.
|
|
452
|
+
Submit only the public event with `submitNativeNostr(identityId, { intent, event,
|
|
453
|
+
approve: true })`. Preserve that exact event on uncertain retries. Check
|
|
454
|
+
`getNativeNostrPost(identityId, eventId)` for relay visibility. An owned note can
|
|
455
|
+
prepare `{ action: 'delete', postId }`; deletion is a signed request, not a
|
|
456
|
+
promise of erasure across all relays. Keys are never sent to this SDK service.
|
|
457
|
+
Preparation and submission never retry automatically, even when `maxRetries` is
|
|
458
|
+
configured. After an uncertain submission, check
|
|
459
|
+
`getNativeNostrPost(identityId, preview.postId ?? preview.eventId)`: deletion uses
|
|
460
|
+
the original note's `postId`, while publication uses the new `eventId`. Any
|
|
461
|
+
user-selected retry must keep the same intent and signed event.
|
package/dist/client.d.ts
CHANGED
|
@@ -30,7 +30,9 @@ export declare class HttpClient {
|
|
|
30
30
|
private getAuthHeaders;
|
|
31
31
|
private handleResponse;
|
|
32
32
|
get<T>(path: string, params?: Record<string, unknown>): Promise<T>;
|
|
33
|
-
post<T>(path: string, body?: unknown
|
|
33
|
+
post<T>(path: string, body?: unknown, options?: {
|
|
34
|
+
retry?: boolean;
|
|
35
|
+
}): Promise<T>;
|
|
34
36
|
put<T>(path: string, body?: unknown): Promise<T>;
|
|
35
37
|
patch<T>(path: string, body?: unknown): Promise<T>;
|
|
36
38
|
delete<T>(path: string): Promise<T>;
|
package/dist/client.js
CHANGED
|
@@ -165,16 +165,17 @@ export class HttpClient {
|
|
|
165
165
|
});
|
|
166
166
|
return this.handleResponse(response);
|
|
167
167
|
}
|
|
168
|
-
async post(path, body) {
|
|
168
|
+
async post(path, body, options) {
|
|
169
169
|
const url = this.buildUrl(path);
|
|
170
|
-
const
|
|
170
|
+
const request = {
|
|
171
171
|
method: 'POST',
|
|
172
172
|
headers: {
|
|
173
173
|
...this.getAuthHeaders(),
|
|
174
174
|
'Content-Type': 'application/json',
|
|
175
175
|
},
|
|
176
176
|
body: body ? JSON.stringify(body) : undefined,
|
|
177
|
-
}
|
|
177
|
+
};
|
|
178
|
+
const response = await (options?.retry === false ? this.fetchWithTimeout(url, request) : this.fetchWithRetry(url, request));
|
|
178
179
|
return this.handleResponse(response);
|
|
179
180
|
}
|
|
180
181
|
async put(path, body) {
|
package/dist/index.d.ts
CHANGED
|
@@ -189,7 +189,7 @@ export type { UploadMediaInput, UploadUrlInput, UploadUrlResult } from './resour
|
|
|
189
189
|
export { WalletResource } from './resources/wallet.js';
|
|
190
190
|
export { ProtocolsResource } from './resources/protocols.js';
|
|
191
191
|
export { PROTOCOL_ADAPTER_MATURITIES, PROTOCOL_CAPABILITY_OPERATIONS, PROTOCOL_CAPABILITY_STATUSES, } from './resources/protocols.js';
|
|
192
|
-
export type { SocialProfile, SocialSubscription, SocialSubscriptionChange, SocialProfilePage, SocialNetwork, SocialPost, SocialConversation, SocialAuthorFeed, SocialFollowingFeed, SocialFollowingSource, SearchPublicProfilesParams, HostedProtocolConnections, HostedProtocol, HostedProtocolIdentity, HostedNostrChallenge, HostedNostrIdentityProof, HostedPostPreview, ConnectHostedIdentityInput, PreviewHostedPostInput, PublishHostedPostInput, ListProtocolCandidatesParams, ProtocolAdapterMaturity, ProtocolAdapterMetadata, ProtocolCandidateListResponse, ProtocolCandidatePageMeta, ProtocolCapabilityOperation, ProtocolCapabilityStatus, ProtocolCollectionStatus, ProtocolQuarantinedCandidate, ProtocolRefreshResult, ProtocolSettings, ProtocolSettingsUpdateResult, UpdateSettingsInput as UpdateProtocolSettingsInput, } from './resources/protocols.js';
|
|
192
|
+
export type { SocialProfile, SocialSubscription, SocialSubscriptionChange, SocialProfilePage, SocialNetwork, SocialPost, SocialConversation, SocialTrend, SocialTrendProvenance, SocialTrends, SocialTrendFeed, SocialAuthorFeed, SocialFollowingFeed, SocialFollowingSource, SearchPublicProfilesParams, HostedProtocolConnections, HostedProtocol, HostedProtocolIdentity, HostedNostrChallenge, HostedNostrIdentityProof, NativeNostrPrepareInput, NativeNostrPrepared, NativeNostrAccount, NativeNostrPostState, NativeNostrReceipt, HostedPostPreview, ConnectHostedIdentityInput, PreviewHostedPostInput, PublishHostedPostInput, ListProtocolCandidatesParams, ProtocolAdapterMaturity, ProtocolAdapterMetadata, ProtocolCandidateListResponse, ProtocolCandidatePageMeta, ProtocolCapabilityOperation, ProtocolCapabilityStatus, ProtocolCollectionStatus, ProtocolQuarantinedCandidate, ProtocolRefreshResult, ProtocolSettings, ProtocolSettingsUpdateResult, UpdateSettingsInput as UpdateProtocolSettingsInput, } from './resources/protocols.js';
|
|
193
193
|
export { InboxResource } from './resources/inbox.js';
|
|
194
194
|
export { NetworkResource } from './resources/network.js';
|
|
195
195
|
export { SimulatorResource } from './resources/simulator.js';
|
|
@@ -159,6 +159,55 @@ export interface HostedNostrIdentityProof {
|
|
|
159
159
|
created_at: number;
|
|
160
160
|
tags: string[][];
|
|
161
161
|
}
|
|
162
|
+
export type NativeNostrPrepareInput = {
|
|
163
|
+
action: 'publish';
|
|
164
|
+
content: string;
|
|
165
|
+
} | {
|
|
166
|
+
action: 'delete';
|
|
167
|
+
postId: string;
|
|
168
|
+
};
|
|
169
|
+
export interface NativeNostrPrepared {
|
|
170
|
+
intent: string;
|
|
171
|
+
event: {
|
|
172
|
+
kind: number;
|
|
173
|
+
created_at: number;
|
|
174
|
+
content: string;
|
|
175
|
+
tags: string[][];
|
|
176
|
+
};
|
|
177
|
+
eventId: string;
|
|
178
|
+
action: 'publish' | 'delete';
|
|
179
|
+
identityId: string;
|
|
180
|
+
pubkey: string;
|
|
181
|
+
relay: string;
|
|
182
|
+
expiresAt: string;
|
|
183
|
+
postId?: string;
|
|
184
|
+
}
|
|
185
|
+
export interface NativeNostrAccount {
|
|
186
|
+
enabled: boolean;
|
|
187
|
+
identity: HostedProtocolIdentity | null;
|
|
188
|
+
capabilities: Array<'publish_original_text' | 'request_own_post_deletion'>;
|
|
189
|
+
signing?: 'client';
|
|
190
|
+
}
|
|
191
|
+
export interface NativeNostrPostState {
|
|
192
|
+
postId: string;
|
|
193
|
+
relay: string;
|
|
194
|
+
pubkey: string;
|
|
195
|
+
state: 'published' | 'deletion_requested' | 'not_found';
|
|
196
|
+
post: {
|
|
197
|
+
content: string;
|
|
198
|
+
createdAt: string;
|
|
199
|
+
} | null;
|
|
200
|
+
}
|
|
201
|
+
export interface NativeNostrReceipt {
|
|
202
|
+
eventId: string;
|
|
203
|
+
postId: string;
|
|
204
|
+
relay: string;
|
|
205
|
+
action: 'publish' | 'delete';
|
|
206
|
+
state: 'published' | 'deletion_requested' | 'unknown' | 'rejected';
|
|
207
|
+
acknowledged: boolean;
|
|
208
|
+
readBack: 'found' | 'absent' | 'unavailable';
|
|
209
|
+
reason?: 'blocked' | 'rate_limited' | 'restricted' | 'invalid' | 'relay_error';
|
|
210
|
+
}
|
|
162
211
|
export type ConnectHostedIdentityInput = {
|
|
163
212
|
protocol: 'atproto';
|
|
164
213
|
identifier: string; /** Prefer a native app password. Used once, never stored. */
|
|
@@ -201,7 +250,7 @@ export interface SocialNetwork {
|
|
|
201
250
|
url: string;
|
|
202
251
|
};
|
|
203
252
|
coverage: string;
|
|
204
|
-
capabilities: Array<'profile_search' | 'profile_read' | 'original_author_posts' | 'minds_subscription' | 'following_feed' | 'conversation_read'>;
|
|
253
|
+
capabilities: Array<'profile_search' | 'profile_read' | 'original_author_posts' | 'minds_subscription' | 'following_feed' | 'conversation_read' | 'public_trends' | 'trend_feed'>;
|
|
205
254
|
profileSearchPagination: boolean;
|
|
206
255
|
conversationCoverage?: string;
|
|
207
256
|
}
|
|
@@ -269,6 +318,39 @@ export interface SocialAuthorFeed {
|
|
|
269
318
|
cursor: string | null;
|
|
270
319
|
coverage: 'original_posts';
|
|
271
320
|
}
|
|
321
|
+
export interface SocialTrendProvenance {
|
|
322
|
+
provider: {
|
|
323
|
+
name: string;
|
|
324
|
+
url: string;
|
|
325
|
+
};
|
|
326
|
+
observedAt: string;
|
|
327
|
+
coverage: 'provider_ranked_window';
|
|
328
|
+
/** Exact ranking interval is not supplied by these providers. */
|
|
329
|
+
rankingWindow: null;
|
|
330
|
+
}
|
|
331
|
+
export interface SocialTrend {
|
|
332
|
+
protocol: string;
|
|
333
|
+
nativeId: string;
|
|
334
|
+
title: string;
|
|
335
|
+
description: string;
|
|
336
|
+
sourceUrl: string;
|
|
337
|
+
startedAt: string | null;
|
|
338
|
+
postCount: number | null;
|
|
339
|
+
}
|
|
340
|
+
export type SocialTrends = SocialTrendProvenance & ({
|
|
341
|
+
kind: 'topics';
|
|
342
|
+
items: SocialTrend[];
|
|
343
|
+
cursor: null;
|
|
344
|
+
} | {
|
|
345
|
+
kind: 'posts';
|
|
346
|
+
items: SocialPost[];
|
|
347
|
+
cursor: string | null;
|
|
348
|
+
});
|
|
349
|
+
export interface SocialTrendFeed extends SocialTrendProvenance {
|
|
350
|
+
trend: SocialTrend;
|
|
351
|
+
items: SocialPost[];
|
|
352
|
+
cursor: string | null;
|
|
353
|
+
}
|
|
272
354
|
export interface SocialConversation {
|
|
273
355
|
focus: SocialPost;
|
|
274
356
|
/** Oldest available ancestor first, ending with the immediate parent. */
|
|
@@ -324,6 +406,31 @@ export declare class ProtocolsResource {
|
|
|
324
406
|
post: string;
|
|
325
407
|
readRef?: string;
|
|
326
408
|
}): Promise<SingleResponse<SocialConversation>>;
|
|
409
|
+
/** Current provider-ranked topics or posts, with explicit source and coverage. Does not ingest content. */
|
|
410
|
+
getPublicTrends(params: {
|
|
411
|
+
protocol: string;
|
|
412
|
+
limit?: number;
|
|
413
|
+
cursor?: string;
|
|
414
|
+
}): Promise<SingleResponse<SocialTrends>>;
|
|
415
|
+
/** Open a topic currently listed by the provider. Use its nativeId as topic and preserve the opaque cursor. */
|
|
416
|
+
getPublicTrendFeed(params: {
|
|
417
|
+
protocol: string;
|
|
418
|
+
topic: string;
|
|
419
|
+
limit?: number;
|
|
420
|
+
cursor?: string;
|
|
421
|
+
}): Promise<SingleResponse<SocialTrendFeed>>;
|
|
422
|
+
/** Native writes are off unless enabled for this exact app; public readers are independent. */
|
|
423
|
+
nativeNostrAccount(): Promise<SingleResponse<NativeNostrAccount>>;
|
|
424
|
+
/** Prepare an exact event for review. A timeout never automatically creates another signing request. */
|
|
425
|
+
prepareNativeNostr(id: string, input: NativeNostrPrepareInput): Promise<SingleResponse<NativeNostrPrepared>>;
|
|
426
|
+
/** Pass only the public signed event. After a timeout, read the prepared postId (delete) or eventId (publish).
|
|
427
|
+
* Any user-selected retry keeps the same intent/event. */
|
|
428
|
+
submitNativeNostr(id: string, input: {
|
|
429
|
+
intent: string;
|
|
430
|
+
event: HostedNostrIdentityProof;
|
|
431
|
+
approve: true;
|
|
432
|
+
}): Promise<SingleResponse<NativeNostrReceipt>>;
|
|
433
|
+
getNativeNostrPost(id: string, postId: string): Promise<SingleResponse<NativeNostrPostState>>;
|
|
327
434
|
/** Save a private reading subscription after resolving the immutable native ID. */
|
|
328
435
|
subscribeToProfile(input: {
|
|
329
436
|
protocol: string;
|
|
@@ -82,6 +82,39 @@ export class ProtocolsResource {
|
|
|
82
82
|
getPublicConversation(params) {
|
|
83
83
|
return this.client.get('/protocols/social/conversation', { protocol: params.protocol, post: params.post, readRef: params.readRef });
|
|
84
84
|
}
|
|
85
|
+
/** Current provider-ranked topics or posts, with explicit source and coverage. Does not ingest content. */
|
|
86
|
+
getPublicTrends(params) {
|
|
87
|
+
return this.client.get('/protocols/social/trends', { protocol: params.protocol, limit: params.limit, cursor: params.cursor });
|
|
88
|
+
}
|
|
89
|
+
/** Open a topic currently listed by the provider. Use its nativeId as topic and preserve the opaque cursor. */
|
|
90
|
+
getPublicTrendFeed(params) {
|
|
91
|
+
return this.client.get('/protocols/social/trend-feed', { protocol: params.protocol, topic: params.topic, limit: params.limit, cursor: params.cursor });
|
|
92
|
+
}
|
|
93
|
+
/** Native writes are off unless enabled for this exact app; public readers are independent. */
|
|
94
|
+
nativeNostrAccount() {
|
|
95
|
+
return this.client.get('/protocols/social/nostr/account');
|
|
96
|
+
}
|
|
97
|
+
/** Prepare an exact event for review. A timeout never automatically creates another signing request. */
|
|
98
|
+
prepareNativeNostr(id, input) {
|
|
99
|
+
hostedIdentityPath(id);
|
|
100
|
+
const body = input.action === 'publish' ? { action: input.action, content: input.content } : { action: input.action, postId: input.postId };
|
|
101
|
+
return this.client.post(`/protocols/social/nostr/${encodeURIComponent(id)}/prepare`, body, { retry: false });
|
|
102
|
+
}
|
|
103
|
+
/** Pass only the public signed event. After a timeout, read the prepared postId (delete) or eventId (publish).
|
|
104
|
+
* Any user-selected retry keeps the same intent/event. */
|
|
105
|
+
submitNativeNostr(id, input) {
|
|
106
|
+
hostedIdentityPath(id);
|
|
107
|
+
const { id: eventId, pubkey, sig, kind, content, created_at, tags } = input.event;
|
|
108
|
+
return this.client.post(`/protocols/social/nostr/${encodeURIComponent(id)}/submit`, {
|
|
109
|
+
intent: input.intent, event: { id: eventId, pubkey, sig, kind, content, created_at, tags }, approve: input.approve,
|
|
110
|
+
}, { retry: false });
|
|
111
|
+
}
|
|
112
|
+
getNativeNostrPost(id, postId) {
|
|
113
|
+
hostedIdentityPath(id);
|
|
114
|
+
if (!/^[0-9a-f]{64}$/.test(postId))
|
|
115
|
+
throw new ValidationError({ type: 'validation_error', code: 'invalid_post_id', message: 'A Nostr event ID is required' });
|
|
116
|
+
return this.client.get(`/protocols/social/nostr/${encodeURIComponent(id)}/posts/${postId}`);
|
|
117
|
+
}
|
|
85
118
|
/** Save a private reading subscription after resolving the immutable native ID. */
|
|
86
119
|
subscribeToProfile(input) {
|
|
87
120
|
return this.client.post('/protocols/social/subscriptions', input).then(result => {
|
package/llm.md
CHANGED
|
@@ -619,7 +619,9 @@ if (subscription.active) {
|
|
|
619
619
|
- `r.admin` — Admin operations (user management, stats, network settings); content reports: `listReports({ status? })`, `resolveReport(id)`, `dismissReport(id)` (project-bound keys see only their app's reports)
|
|
620
620
|
- `r.inviteCodes` / `r.inviteCodesAdmin` — Invite code management
|
|
621
621
|
- `r.organizationSettings` / `r.organizationSecurity` — Org configuration
|
|
622
|
-
- `r.protocols` — Public reading: `publicNetworks`, `searchPublicProfiles`, `getPublicProfile`, `getPublicAuthorFeed`, `getFollowingFeed`, `getPublicConversation`; 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`.
|
|
622
|
+
- `r.protocols` — 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`.
|
|
623
|
+
- 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.
|
|
624
|
+
- 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.
|
|
623
625
|
- `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.
|
|
624
626
|
- Lists return `{ data: ProtocolQuarantinedCandidate[], meta: { limit, has_more, next_cursor } }`; use the opaque `next_cursor`, not offset pagination. The API defaults `limit` to 20 and permits at most 50. Details return `{ data: ProtocolQuarantinedCandidate }`. Every candidate has `state: 'quarantined'` and `content_format: 'plain'`; render its bounded `content` as text, respect `content_truncated`, and distinguish upstream `published_at` from local `observed_at` / `created_at`.
|
|
625
627
|
- Safe source IDs are preserved byte-exact. `external_id: null` means unsafe or malformed provenance was withheld, not an invented ID; `author.native_id` may likewise be absent or withheld. Preserve nulls in both list and detail views, and use the candidate's own `id` for `getCandidate`.
|