@recursiv/sdk 0.7.58 → 0.7.59
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 +16 -4
- package/dist/errors.d.ts +13 -0
- package/dist/errors.js +12 -0
- package/dist/index.d.ts +7 -6
- package/dist/index.js +2 -2
- package/dist/resources/agents.d.ts +70 -0
- package/dist/resources/agents.js +10 -0
- package/dist/resources/curator.d.ts +5 -1
- package/dist/resources/curator.js +6 -2
- package/dist/resources/discovery.d.ts +83 -1
- package/dist/resources/discovery.js +36 -6
- package/dist/resources/e2ee.d.ts +87 -2
- package/dist/resources/e2ee.js +31 -2
- package/dist/resources/email.d.ts +27 -3
- package/dist/resources/email.js +18 -3
- package/dist/resources/env-vars.d.ts +25 -0
- package/dist/resources/env-vars.js +8 -0
- package/dist/resources/moderation.d.ts +22 -0
- package/dist/resources/moderation.js +16 -0
- package/dist/resources/posts.d.ts +1 -1
- package/dist/resources/posts.js +6 -2
- package/dist/resources/protocols.d.ts +209 -2
- package/dist/resources/protocols.js +57 -1
- package/dist/types.d.ts +50 -0
- package/llm.md +16 -5
- package/package.json +1 -1
package/dist/resources/e2ee.d.ts
CHANGED
|
@@ -20,7 +20,54 @@ export interface E2eeDevice {
|
|
|
20
20
|
* before 0.7.57.
|
|
21
21
|
*/
|
|
22
22
|
lastSeenAt?: string | null;
|
|
23
|
+
/**
|
|
24
|
+
* Set on a root that a recorded reset of encrypted chat retired
|
|
25
|
+
* (`resetAccount`): when the old main device was revoked. Null on every
|
|
26
|
+
* other device, and on a root that stopped being the root without a
|
|
27
|
+
* recorded reset: treat that change as unexplained (compare security codes),
|
|
28
|
+
* never as a reset. Absent on relays without resets.
|
|
29
|
+
*/
|
|
30
|
+
rootRetiredAt?: string | null;
|
|
31
|
+
}
|
|
32
|
+
/** `requestResetCode`'s answer: when the emailed code expires, and the masked address it went to. */
|
|
33
|
+
export interface E2eeResetCodeSent {
|
|
34
|
+
expires_at: string;
|
|
35
|
+
/** Masked, e.g. `b***@minds.com`: show it so the person knows which inbox to open. */
|
|
36
|
+
sent_to: string;
|
|
37
|
+
}
|
|
38
|
+
/** What `resetAccount` did. The account's encrypted chat starts again from `device`. */
|
|
39
|
+
export interface E2eeAccountReset {
|
|
40
|
+
reset: true;
|
|
41
|
+
resetAt: string;
|
|
42
|
+
/** The main device the reset retired; null when the account had none. */
|
|
43
|
+
retiredRoot: {
|
|
44
|
+
deviceId: string;
|
|
45
|
+
signingKey: string;
|
|
46
|
+
} | null;
|
|
47
|
+
/** Every device the reset revoked. */
|
|
48
|
+
revokedDeviceIds: string[];
|
|
49
|
+
/** This device, registered as the account's main device (root). */
|
|
50
|
+
device: E2eeDevice;
|
|
51
|
+
/**
|
|
52
|
+
* How many other sessions and API keys of the account were signed out, and
|
|
53
|
+
* how many AI clients connected over MCP OAuth were disconnected.
|
|
54
|
+
*/
|
|
55
|
+
signedOut: {
|
|
56
|
+
sessions: number;
|
|
57
|
+
keys: number;
|
|
58
|
+
connectedApps: number;
|
|
59
|
+
};
|
|
23
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Codes a reset, or a call a reset may be needed for, can be refused with:
|
|
63
|
+
* - `confirmation_failed` (403): the code is wrong, expired, spent or dead after five wrong tries.
|
|
64
|
+
* - `code_limit` (409, never 429): too many codes asked for; `details.retryAfterSeconds`.
|
|
65
|
+
* - `no_email_on_file` (400), `email_delivery_unavailable` (503): no code could be sent.
|
|
66
|
+
* - `reset_required` (409): from `createLink` when the account's main device is gone with
|
|
67
|
+
* nothing left to approve a new one, and from `revokeDevice` on the main device. Only a reset goes on.
|
|
68
|
+
* - `conflict` (409): the device name or key was used before; start the device again with fresh ones.
|
|
69
|
+
*/
|
|
70
|
+
export type E2eeResetErrorCode = 'confirmation_failed' | 'code_limit' | 'no_email_on_file' | 'email_delivery_unavailable' | 'reset_required' | 'conflict';
|
|
24
71
|
/** Opaque `recursiv-mls-v2-draft1` envelope with base64 MLS bytes. */
|
|
25
72
|
export interface E2eeEnvelope {
|
|
26
73
|
profile: 'recursiv-mls-v2-draft1';
|
|
@@ -111,6 +158,12 @@ export interface E2eeStatus {
|
|
|
111
158
|
* makes, never switch to it on their behalf.
|
|
112
159
|
*/
|
|
113
160
|
links?: boolean;
|
|
161
|
+
/**
|
|
162
|
+
* True when this network offers this account resetting encrypted chat
|
|
163
|
+
* (`requestResetCode`, `resetAccount`); never for an account the reset would
|
|
164
|
+
* refuse, such as a platform owner's. False or absent: do not offer it.
|
|
165
|
+
*/
|
|
166
|
+
accountReset?: boolean;
|
|
114
167
|
}
|
|
115
168
|
/** A device link's state. An open link past `expiresAt` reads as `cancelled` with `cancelledBy: 'expired'`. */
|
|
116
169
|
export type E2eeLinkState = 'open' | 'approved' | 'completed' | 'cancelled';
|
|
@@ -227,11 +280,41 @@ export declare class E2eeResource {
|
|
|
227
280
|
* `details: E2eeHasDependentsDetails`, so the app can say which devices
|
|
228
281
|
* would be signed out. Call again with `{ cascade: true }` to revoke it and
|
|
229
282
|
* every device chained through it (relays from 0.7.58; older relays ignore
|
|
230
|
-
* the body and revoke only the device).
|
|
283
|
+
* the body and revoke only the device). Where this account is offered
|
|
284
|
+
* resetting encrypted chat (`status().accountReset`), the main device is never
|
|
285
|
+
* revoked this way: 409 `reset_required`; replace it with `resetAccount`.
|
|
231
286
|
*/
|
|
232
287
|
revokeDevice(deviceId: string, input?: {
|
|
233
288
|
cascade?: boolean;
|
|
234
289
|
}): Promise<E2eeRevokeResponse>;
|
|
290
|
+
/**
|
|
291
|
+
* Email this account an eight-digit code to reset encrypted chat, and warn
|
|
292
|
+
* its owner that one was asked for. Only where `status().accountReset` is
|
|
293
|
+
* true. The account's code budget is shared with other confirmation codes:
|
|
294
|
+
* over it, a `ConflictError` with code `code_limit` and
|
|
295
|
+
* `details.retryAfterSeconds` (never a 429). Not retried.
|
|
296
|
+
*/
|
|
297
|
+
requestResetCode(): Promise<SingleResponse<E2eeResetCodeSent>>;
|
|
298
|
+
/**
|
|
299
|
+
* Reset encrypted chat with the emailed code, and make this device the
|
|
300
|
+
* account's main device: pass it as `registerDevice` takes it, with a fresh
|
|
301
|
+
* name and key and its own self-signed credential. Every other device of
|
|
302
|
+
* the account is removed from encrypted chat, and the account is signed out
|
|
303
|
+
* of every other session and API key on this network (`keep_session_token`
|
|
304
|
+
* keeps a Better Auth session this device holds). Contacts see that the
|
|
305
|
+
* security code changed. A wrong, expired or spent code: `confirmation_failed`
|
|
306
|
+
* (403), nothing changed. Sending the same request again after it succeeded
|
|
307
|
+
* (from the same sign-in) answers the same result, so a retry after a lost
|
|
308
|
+
* answer is safe.
|
|
309
|
+
*/
|
|
310
|
+
resetAccount(input: {
|
|
311
|
+
email_code: string;
|
|
312
|
+
device_name: string;
|
|
313
|
+
realm: string;
|
|
314
|
+
signing_key: string;
|
|
315
|
+
credential: Record<string, unknown>;
|
|
316
|
+
keep_session_token?: string;
|
|
317
|
+
}): Promise<SingleResponse<E2eeAccountReset>>;
|
|
235
318
|
/** Publish one-time key packages for one of your devices; `available` is how many it now has. */
|
|
236
319
|
publishKeyPackages(input: {
|
|
237
320
|
device_id: string;
|
|
@@ -328,7 +411,9 @@ export declare class E2eeResource {
|
|
|
328
411
|
/**
|
|
329
412
|
* Start linking this new, not yet registered device: `POST /e2ee/links`.
|
|
330
413
|
* Answers the open link (180 s on the relay's clock). A new link cancels
|
|
331
|
-
* this sign-in's previous open link. `
|
|
414
|
+
* this sign-in's previous open link. `reset_required` (409): the account's
|
|
415
|
+
* main device is gone and nothing is left to approve this one; only
|
|
416
|
+
* `resetAccount` goes on. `label` is 1-48 letters, numbers,
|
|
332
417
|
* spaces and `. , ' ( ) + -`; reserved words are refused (`invalid_label`).
|
|
333
418
|
* Not retried: a 5xx is answered at once, and the caller decides.
|
|
334
419
|
*/
|
package/dist/resources/e2ee.js
CHANGED
|
@@ -35,11 +35,38 @@ export class E2eeResource {
|
|
|
35
35
|
* `details: E2eeHasDependentsDetails`, so the app can say which devices
|
|
36
36
|
* would be signed out. Call again with `{ cascade: true }` to revoke it and
|
|
37
37
|
* every device chained through it (relays from 0.7.58; older relays ignore
|
|
38
|
-
* the body and revoke only the device).
|
|
38
|
+
* the body and revoke only the device). Where this account is offered
|
|
39
|
+
* resetting encrypted chat (`status().accountReset`), the main device is never
|
|
40
|
+
* revoked this way: 409 `reset_required`; replace it with `resetAccount`.
|
|
39
41
|
*/
|
|
40
42
|
revokeDevice(deviceId, input) {
|
|
41
43
|
return this.client.post(`/e2ee/devices/${encodeURIComponent(deviceId)}/revoke`, input ?? {});
|
|
42
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Email this account an eight-digit code to reset encrypted chat, and warn
|
|
47
|
+
* its owner that one was asked for. Only where `status().accountReset` is
|
|
48
|
+
* true. The account's code budget is shared with other confirmation codes:
|
|
49
|
+
* over it, a `ConflictError` with code `code_limit` and
|
|
50
|
+
* `details.retryAfterSeconds` (never a 429). Not retried.
|
|
51
|
+
*/
|
|
52
|
+
requestResetCode() {
|
|
53
|
+
return this.client.post('/e2ee/account/reset-code', {}, { retry: false });
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Reset encrypted chat with the emailed code, and make this device the
|
|
57
|
+
* account's main device: pass it as `registerDevice` takes it, with a fresh
|
|
58
|
+
* name and key and its own self-signed credential. Every other device of
|
|
59
|
+
* the account is removed from encrypted chat, and the account is signed out
|
|
60
|
+
* of every other session and API key on this network (`keep_session_token`
|
|
61
|
+
* keeps a Better Auth session this device holds). Contacts see that the
|
|
62
|
+
* security code changed. A wrong, expired or spent code: `confirmation_failed`
|
|
63
|
+
* (403), nothing changed. Sending the same request again after it succeeded
|
|
64
|
+
* (from the same sign-in) answers the same result, so a retry after a lost
|
|
65
|
+
* answer is safe.
|
|
66
|
+
*/
|
|
67
|
+
resetAccount(input) {
|
|
68
|
+
return this.client.post('/e2ee/account/reset', input, { retry: true });
|
|
69
|
+
}
|
|
43
70
|
/** Publish one-time key packages for one of your devices; `available` is how many it now has. */
|
|
44
71
|
publishKeyPackages(input) {
|
|
45
72
|
return this.client.post('/e2ee/key-packages', input);
|
|
@@ -109,7 +136,9 @@ export class E2eeResource {
|
|
|
109
136
|
/**
|
|
110
137
|
* Start linking this new, not yet registered device: `POST /e2ee/links`.
|
|
111
138
|
* Answers the open link (180 s on the relay's clock). A new link cancels
|
|
112
|
-
* this sign-in's previous open link. `
|
|
139
|
+
* this sign-in's previous open link. `reset_required` (409): the account's
|
|
140
|
+
* main device is gone and nothing is left to approve this one; only
|
|
141
|
+
* `resetAccount` goes on. `label` is 1-48 letters, numbers,
|
|
113
142
|
* spaces and `. , ' ( ) + -`; reserved words are refused (`invalid_label`).
|
|
114
143
|
* Not retried: a 5xx is answered at once, and the caller decides.
|
|
115
144
|
*/
|
|
@@ -112,15 +112,39 @@ export interface SendEmailInput {
|
|
|
112
112
|
reply_to?: string;
|
|
113
113
|
/** File attachments (max 10). Content must be base64-encoded. */
|
|
114
114
|
attachments?: EmailAttachment[];
|
|
115
|
+
/**
|
|
116
|
+
* Defaults to `'marketing'`, which is held unless the recipient has given
|
|
117
|
+
* marketing consent. Pass `'transactional'` for receipts, invites,
|
|
118
|
+
* confirmations and alerts.
|
|
119
|
+
*/
|
|
120
|
+
category?: 'transactional' | 'product' | 'marketing';
|
|
121
|
+
}
|
|
122
|
+
export interface SendEmailMessage {
|
|
123
|
+
id: string | null;
|
|
124
|
+
to: string;
|
|
125
|
+
/** `queued`/`sent`/`retry` are on their way; `skipped`/`suppressed`/`failed`/`duplicate` are not. */
|
|
126
|
+
status: 'queued' | 'sent' | 'retry' | 'failed' | 'suppressed' | 'skipped' | 'duplicate' | (string & {});
|
|
127
|
+
/** Why a held recipient was not sent, e.g. "marketing consent not granted". */
|
|
128
|
+
reason?: string;
|
|
115
129
|
}
|
|
116
130
|
export interface SendEmailResult {
|
|
117
|
-
id: string;
|
|
118
|
-
|
|
131
|
+
id: string | null;
|
|
132
|
+
/** `queued`: every recipient; `partial`: some held (see `messages`). A send where all were held throws `EmailNotSentError`. */
|
|
133
|
+
status: 'queued' | 'partial' | 'skipped' | (string & {});
|
|
134
|
+
messages?: SendEmailMessage[];
|
|
135
|
+
/** Present when any recipient was held: says why, in words. */
|
|
136
|
+
hint?: string;
|
|
119
137
|
}
|
|
120
138
|
export declare class EmailResource {
|
|
121
139
|
private client;
|
|
122
140
|
constructor(client: HttpClient);
|
|
123
|
-
/**
|
|
141
|
+
/**
|
|
142
|
+
* Send an email as the organization (its BYOK Resend key when configured).
|
|
143
|
+
* Set `category: 'transactional'` for receipts, invites and alerts: the
|
|
144
|
+
* default is `'marketing'`, which needs the recipient's consent.
|
|
145
|
+
* Throws `EmailNotSentError` when every recipient was held; check
|
|
146
|
+
* `status === 'partial'` and `messages` for a send that reached only some.
|
|
147
|
+
*/
|
|
124
148
|
send(input: SendEmailInput): Promise<SingleResponse<SendEmailResult>>;
|
|
125
149
|
/** List all campaigns */
|
|
126
150
|
listCampaigns(): Promise<SingleResponse<CampaignSummary[]>>;
|
package/dist/resources/email.js
CHANGED
|
@@ -1,11 +1,26 @@
|
|
|
1
|
+
import { EmailNotSentError } from '../errors.js';
|
|
1
2
|
export class EmailResource {
|
|
2
3
|
client;
|
|
3
4
|
constructor(client) {
|
|
4
5
|
this.client = client;
|
|
5
6
|
}
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Send an email as the organization (its BYOK Resend key when configured).
|
|
9
|
+
* Set `category: 'transactional'` for receipts, invites and alerts: the
|
|
10
|
+
* default is `'marketing'`, which needs the recipient's consent.
|
|
11
|
+
* Throws `EmailNotSentError` when every recipient was held; check
|
|
12
|
+
* `status === 'partial'` and `messages` for a send that reached only some.
|
|
13
|
+
*/
|
|
14
|
+
async send(input) {
|
|
15
|
+
const res = (await this.client.post('/email/send', input));
|
|
16
|
+
if (res?.data?.status === 'skipped') {
|
|
17
|
+
throw new EmailNotSentError({
|
|
18
|
+
message: 'Email not sent: every recipient was held.',
|
|
19
|
+
hint: res.data.hint,
|
|
20
|
+
details: res.data,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return res;
|
|
9
24
|
}
|
|
10
25
|
/** List all campaigns */
|
|
11
26
|
listCampaigns() {
|
|
@@ -37,6 +37,25 @@ export interface DeleteEnvVarParams {
|
|
|
37
37
|
/** Omit to remove the variable from every environment. */
|
|
38
38
|
environment?: EnvVarEnvironment;
|
|
39
39
|
}
|
|
40
|
+
export interface CreateProjectApiKeyInput {
|
|
41
|
+
project_id: string;
|
|
42
|
+
/** The env var that receives the key. UPPER_SNAKE_CASE, e.g. RECURSIV_API_KEY. */
|
|
43
|
+
key: string;
|
|
44
|
+
/** Defaults to databases:read + databases:write. Never more than the calling key holds; never admin. */
|
|
45
|
+
scopes?: string[];
|
|
46
|
+
/** Defaults to 'both'. */
|
|
47
|
+
environment?: EnvVarEnvironment;
|
|
48
|
+
}
|
|
49
|
+
export interface CreateProjectApiKeyResult {
|
|
50
|
+
key: string;
|
|
51
|
+
environment: EnvVarEnvironment;
|
|
52
|
+
api_key_id: string;
|
|
53
|
+
/** The key's public prefix, for recognizing it in a key list. Not the key. */
|
|
54
|
+
api_key_prefix: string;
|
|
55
|
+
scopes: string[];
|
|
56
|
+
/** How many earlier keys minted into this env var were revoked. */
|
|
57
|
+
revoked_previous: number;
|
|
58
|
+
}
|
|
40
59
|
export declare class EnvVarsResource {
|
|
41
60
|
private client;
|
|
42
61
|
constructor(client: HttpClient);
|
|
@@ -47,6 +66,12 @@ export declare class EnvVarsResource {
|
|
|
47
66
|
* changes what runs in production.
|
|
48
67
|
*/
|
|
49
68
|
set(input: SetEnvVarInput): Promise<SingleResponse<Omit<EnvVar, 'value' | 'source' | 'updated_at'>>>;
|
|
69
|
+
/**
|
|
70
|
+
* Mint a project-bound API key straight into an env var. The key value goes
|
|
71
|
+
* to the encrypted variable and is never returned. Running it again rotates:
|
|
72
|
+
* the previous key in that variable is revoked. Requires project admin.
|
|
73
|
+
*/
|
|
74
|
+
createProjectApiKey(input: CreateProjectApiKeyInput): Promise<SingleResponse<CreateProjectApiKeyResult>>;
|
|
50
75
|
delete(params: DeleteEnvVarParams): Promise<SingleResponse<{
|
|
51
76
|
key: string;
|
|
52
77
|
deleted: boolean;
|
|
@@ -17,6 +17,14 @@ export class EnvVarsResource {
|
|
|
17
17
|
async set(input) {
|
|
18
18
|
return this.client.put('/env-vars', input);
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Mint a project-bound API key straight into an env var. The key value goes
|
|
22
|
+
* to the encrypted variable and is never returned. Running it again rotates:
|
|
23
|
+
* the previous key in that variable is revoked. Requires project admin.
|
|
24
|
+
*/
|
|
25
|
+
async createProjectApiKey(input) {
|
|
26
|
+
return this.client.post('/env-vars/api-key', input);
|
|
27
|
+
}
|
|
20
28
|
async delete(params) {
|
|
21
29
|
const query = new URLSearchParams({ project_id: params.project_id, key: params.key });
|
|
22
30
|
if (params.environment)
|
|
@@ -63,6 +63,18 @@ export interface ApplyModerationActionInput {
|
|
|
63
63
|
actor_type?: 'human' | 'ai' | 'system';
|
|
64
64
|
source_report_id?: string;
|
|
65
65
|
}
|
|
66
|
+
/** What a granted agent may do on the report queue: shadow reads it; act also resolves and dismisses. */
|
|
67
|
+
export type AgentModerationMode = 'shadow' | 'act';
|
|
68
|
+
/** A network admin's grant letting one AI agent work the report queue. */
|
|
69
|
+
export interface AgentModerationGrant {
|
|
70
|
+
id: string;
|
|
71
|
+
network_id: string | null;
|
|
72
|
+
agent_id: string;
|
|
73
|
+
mode: AgentModerationMode;
|
|
74
|
+
granted_by_id: string | null;
|
|
75
|
+
created_at: string;
|
|
76
|
+
revoked_at: string | null;
|
|
77
|
+
}
|
|
66
78
|
export declare class ModerationResource {
|
|
67
79
|
private client;
|
|
68
80
|
constructor(client: HttpClient);
|
|
@@ -101,4 +113,14 @@ export declare class ModerationResource {
|
|
|
101
113
|
action_id: string;
|
|
102
114
|
outcome: 'upheld' | 'overturned';
|
|
103
115
|
}>>;
|
|
116
|
+
/** Admin: the network's active agent moderation grants. */
|
|
117
|
+
listAgentGrants(): Promise<SingleResponse<AgentModerationGrant[]>>;
|
|
118
|
+
/**
|
|
119
|
+
* Admin: let an AI agent in the caller's network work the report queue. An
|
|
120
|
+
* active grant is replaced. The agent's own key also needs the
|
|
121
|
+
* `moderation:agent` scope.
|
|
122
|
+
*/
|
|
123
|
+
grantAgent(agentId: string, mode: AgentModerationMode): Promise<SingleResponse<AgentModerationGrant>>;
|
|
124
|
+
/** Admin: revoke an agent's active moderation grant. */
|
|
125
|
+
revokeAgent(agentId: string): Promise<SingleResponse<AgentModerationGrant>>;
|
|
104
126
|
}
|
|
@@ -34,4 +34,20 @@ export class ModerationResource {
|
|
|
34
34
|
resolveAppeal(appealId, input) {
|
|
35
35
|
return this.client.post(`/moderation/appeals/${encodeURIComponent(appealId)}/resolve`, input);
|
|
36
36
|
}
|
|
37
|
+
/** Admin: the network's active agent moderation grants. */
|
|
38
|
+
listAgentGrants() {
|
|
39
|
+
return this.client.get('/moderation/agent-grants');
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Admin: let an AI agent in the caller's network work the report queue. An
|
|
43
|
+
* active grant is replaced. The agent's own key also needs the
|
|
44
|
+
* `moderation:agent` scope.
|
|
45
|
+
*/
|
|
46
|
+
grantAgent(agentId, mode) {
|
|
47
|
+
return this.client.post('/moderation/agent-grants', { agent_id: agentId, mode });
|
|
48
|
+
}
|
|
49
|
+
/** Admin: revoke an agent's active moderation grant. */
|
|
50
|
+
revokeAgent(agentId) {
|
|
51
|
+
return this.client.delete(`/moderation/agent-grants/${encodeURIComponent(agentId)}`);
|
|
52
|
+
}
|
|
37
53
|
}
|
|
@@ -17,7 +17,7 @@ export interface CategoryOrderMeta {
|
|
|
17
17
|
export declare class PostsResource {
|
|
18
18
|
private client;
|
|
19
19
|
constructor(client: HttpClient);
|
|
20
|
-
/** List posts with optional filters */
|
|
20
|
+
/** List posts with optional filters. `retry: false` asks once and is never sent (see ListPostsParams.retry). */
|
|
21
21
|
list(params?: ListPostsParams): Promise<PaginatedResponse<Post>>;
|
|
22
22
|
/**
|
|
23
23
|
* Broad categories currently enabled for public posts in this app, in the order this member should see
|
package/dist/resources/posts.js
CHANGED
|
@@ -3,9 +3,13 @@ export class PostsResource {
|
|
|
3
3
|
constructor(client) {
|
|
4
4
|
this.client = client;
|
|
5
5
|
}
|
|
6
|
-
/** List posts with optional filters */
|
|
6
|
+
/** List posts with optional filters. `retry: false` asks once and is never sent (see ListPostsParams.retry). */
|
|
7
7
|
list(params) {
|
|
8
|
-
|
|
8
|
+
// Without `retry`, the same request older SDKs make.
|
|
9
|
+
if (!params || !('retry' in params))
|
|
10
|
+
return this.client.get('/posts', params);
|
|
11
|
+
const { retry, ...query } = params;
|
|
12
|
+
return retry === false ? this.client.get('/posts', query, { retry: false }) : this.client.get('/posts', query);
|
|
9
13
|
}
|
|
10
14
|
/**
|
|
11
15
|
* Broad categories currently enabled for public posts in this app, in the order this member should see
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { HttpClient } from '../client.js';
|
|
2
|
-
import type { DiscoveryLayout, PaginatedResponse, PaginationParams, SingleResponse } from '../types.js';
|
|
2
|
+
import type { CompassFilterMeta, DiscoveryLayout, PaginatedResponse, PaginationParams, SingleResponse } from '../types.js';
|
|
3
3
|
/** Canonical protocol operation names returned by the adapter registry. */
|
|
4
4
|
export declare const PROTOCOL_CAPABILITY_OPERATIONS: readonly ["discover", "stream", "backfill", "search", "resolveIdentity", "fetchProfile", "fetchThread", "publish", "reply", "react", "repost", "follow", "delete", "health", "checkpoint"];
|
|
5
5
|
export declare const PROTOCOL_CAPABILITY_STATUSES: readonly ["supported", "planned", "unsupported"];
|
|
@@ -906,7 +906,8 @@ export interface SocialNetwork {
|
|
|
906
906
|
url: string;
|
|
907
907
|
};
|
|
908
908
|
coverage: string;
|
|
909
|
-
|
|
909
|
+
/** `compass_filter`: the Politics filter is served to this viewer (an app offers it only then); listed on every network. */
|
|
910
|
+
capabilities: Array<'profile_search' | 'profile_read' | 'original_author_posts' | 'minds_subscription' | 'following_feed' | 'conversation_read' | 'public_trends' | 'trend_feed' | 'popular_accounts' | 'post_search' | 'compass_filter'>;
|
|
910
911
|
profileSearchPagination: boolean;
|
|
911
912
|
postSearchAccountRequired?: boolean;
|
|
912
913
|
/** Categories available from the current app configuration. */
|
|
@@ -930,8 +931,22 @@ export interface SocialProfile {
|
|
|
930
931
|
postsCount: number | null;
|
|
931
932
|
/** Nostr only: the NIP-05 name its domain has confirmed for this key ("lyn@primal.net"); `handle` stays the key. */
|
|
932
933
|
nip05?: string;
|
|
934
|
+
/** A bridge (Bridgy Fed, Mostr) made this account from one on another network (SocialBridge). Present only on a bridged account. */
|
|
935
|
+
bridge?: SocialBridge;
|
|
933
936
|
subscription: SocialSubscription | null;
|
|
934
937
|
}
|
|
938
|
+
/**
|
|
939
|
+
* A post or account a bridge copied from another network: Bridgy Fed (`*.brid.gy`), Mostr (`mostr.pub`), or a Nostr
|
|
940
|
+
* event's NIP-48 `proxy` tag naming no bridge the server knows (`proxy`). `originId` names the original, normalised (a
|
|
941
|
+
* Bluesky at-uri or DID, an ActivityPub object URL or `user@host`, a Nostr event id or key, a web URL), when the bridge
|
|
942
|
+
* says. Where the server reads both, feeds, search and the radar already fold a copy into its original; an app that
|
|
943
|
+
* merges several networks' pages itself can fold the same way: a copy whose original is shown is the same post.
|
|
944
|
+
*/
|
|
945
|
+
export interface SocialBridge {
|
|
946
|
+
via: 'bridgy_fed' | 'mostr' | 'proxy';
|
|
947
|
+
protocol: 'bluesky' | 'activitypub' | 'nostr' | 'web';
|
|
948
|
+
originId?: string;
|
|
949
|
+
}
|
|
935
950
|
export interface SocialSubscription {
|
|
936
951
|
id: string;
|
|
937
952
|
protocol: string;
|
|
@@ -1028,6 +1043,8 @@ export interface SocialPost {
|
|
|
1028
1043
|
* to signed-out visitors.
|
|
1029
1044
|
*/
|
|
1030
1045
|
authorOptOut?: true;
|
|
1046
|
+
/** A bridge (Bridgy Fed, Mostr, a NIP-48 proxy tag) made this post from one on another network (SocialBridge). Present only on a copy read live. */
|
|
1047
|
+
bridge?: SocialBridge;
|
|
1031
1048
|
}
|
|
1032
1049
|
export interface SocialPostSearch {
|
|
1033
1050
|
items: SocialPost[];
|
|
@@ -1290,6 +1307,182 @@ export interface SocialRadarStory {
|
|
|
1290
1307
|
*/
|
|
1291
1308
|
editionStory?: string;
|
|
1292
1309
|
}
|
|
1310
|
+
/**
|
|
1311
|
+
* The Viral read (`getPublicViral`, `getSharedViral`): what is moving fastest on Bluesky, the Fediverse, Nostr and
|
|
1312
|
+
* Minds right now, each measured against its own network's pace, from a snapshot the server's rank tick rebuilds about
|
|
1313
|
+
* every five minutes. A read never reaches a provider or the model. These types mirror the server's
|
|
1314
|
+
* ViralSnapshotContract; a contract test compares them key by key.
|
|
1315
|
+
*/
|
|
1316
|
+
export type ViralNetwork = 'all' | 'minds' | 'bluesky' | 'activitypub' | 'nostr';
|
|
1317
|
+
export type ViralProtocol = 'bluesky' | 'activitypub' | 'nostr' | 'minds';
|
|
1318
|
+
/** The networks whose posts the read serves as a `SocialPost`. */
|
|
1319
|
+
export type ViralOpenProtocol = 'bluesky' | 'activitypub' | 'nostr';
|
|
1320
|
+
/**
|
|
1321
|
+
* `viral`: a measured rate well above its network's baseline (z of 1 or more) with two or more kinds of engagement;
|
|
1322
|
+
* `rising`: a measured rate speeding up (accel of 0.7 or more) on a post under six hours old; `hot`: everything else,
|
|
1323
|
+
* including every post without a measured rate.
|
|
1324
|
+
*/
|
|
1325
|
+
export type ViralLabel = 'viral' | 'rising' | 'hot';
|
|
1326
|
+
/**
|
|
1327
|
+
* `ok`: a build served. `building`: no build yet. `screening`: served, but fewer than 30% of the top 200 posts have
|
|
1328
|
+
* a safety verdict yet. `off`: the read is not enabled for this app (or, on `getSharedViral` only, the API predates it):
|
|
1329
|
+
* no items, and nothing to retry until it is turned on.
|
|
1330
|
+
*/
|
|
1331
|
+
export type ViralReadStatus = 'ok' | 'building' | 'screening' | 'off';
|
|
1332
|
+
/** Every parameter is optional. The server rejects any other parameter with a 400, so only these travel. */
|
|
1333
|
+
export interface ViralReadParams {
|
|
1334
|
+
/** Default `all`: every network, with a per-network quota so no one network fills the page. */
|
|
1335
|
+
network?: ViralNetwork;
|
|
1336
|
+
/** A Discover category id: only posts judged into it. */
|
|
1337
|
+
category?: string;
|
|
1338
|
+
/**
|
|
1339
|
+
* `video`: only posts whose own media includes a video. A member's read serves every such video. A visitor's read
|
|
1340
|
+
* (`getSharedViral`) still serves a post's media only where the server cleared it, so some items can arrive with
|
|
1341
|
+
* the video withheld (the card's `media` empty): keep `items.filter(viralItemHasVideo)` for a video-only player.
|
|
1342
|
+
*/
|
|
1343
|
+
media?: 'video';
|
|
1344
|
+
/** Page size, default 20. */
|
|
1345
|
+
limit?: 5 | 10 | 20 | 40;
|
|
1346
|
+
/** The previous page's `cursor`. A cursor names its build; one whose build expired (30 minutes) ends the list. */
|
|
1347
|
+
cursor?: string;
|
|
1348
|
+
/** A Politics choice. A visitor (`getSharedViral`) may send only `all` or `hide`; anything else is a 400. */
|
|
1349
|
+
compass?: string;
|
|
1350
|
+
}
|
|
1351
|
+
/** How fast a post is gathering reactions, reposts and replies (per hour), against its network's baseline. */
|
|
1352
|
+
export interface ViralItemVelocity {
|
|
1353
|
+
perHour1h: number;
|
|
1354
|
+
perHour6h: number;
|
|
1355
|
+
/** The 1-hour rate against its network's trailing baseline, from -2 to 4 (1 or more is well above). */
|
|
1356
|
+
z: number;
|
|
1357
|
+
/** ln((1 + perHour1h) / (1 + perHour6h)), from -1 to 2: above 0 is speeding up. */
|
|
1358
|
+
accel: number;
|
|
1359
|
+
/** Readings behind the rate; null for a Minds post. */
|
|
1360
|
+
samples: number | null;
|
|
1361
|
+
spanHours: number;
|
|
1362
|
+
/** The rate comes from two or more readings over time (a Minds post's always does). An unmeasured post is never viral or rising. */
|
|
1363
|
+
measured: boolean;
|
|
1364
|
+
/** Who counted: Bluesky's AppView, one Fediverse server, a third party's report (Primal, for Nostr), or Minds itself. */
|
|
1365
|
+
countsOrigin: 'appview' | 'instance_local' | 'provider_reported' | 'minds';
|
|
1366
|
+
/** Minutes since the counts were last read, when known. */
|
|
1367
|
+
countsAgeMin?: number;
|
|
1368
|
+
/** Present when a third party reported the counts (`Primal`): show them as reported by it, never as verified. */
|
|
1369
|
+
countsReportedBy?: string;
|
|
1370
|
+
}
|
|
1371
|
+
/** Another post in the same spread group: the same link posted on another network. */
|
|
1372
|
+
export interface ViralRef {
|
|
1373
|
+
protocol: ViralProtocol;
|
|
1374
|
+
nativeId: string;
|
|
1375
|
+
}
|
|
1376
|
+
export interface ViralItemSpread {
|
|
1377
|
+
/** The networks sharing the post's link (its own alone when no other network carries it). */
|
|
1378
|
+
protocols: ViralProtocol[];
|
|
1379
|
+
/** Fediverse: how many servers' trends lists carried the post. */
|
|
1380
|
+
instances?: number;
|
|
1381
|
+
clusterId: null;
|
|
1382
|
+
alsoOn: ViralRef[];
|
|
1383
|
+
}
|
|
1384
|
+
/** A Minds post's card as the read serves it; open it at /post/:id. A visitor's carries media only where the server cleared it. */
|
|
1385
|
+
export interface ViralMindsCard {
|
|
1386
|
+
id: string;
|
|
1387
|
+
title: string | null;
|
|
1388
|
+
text: string;
|
|
1389
|
+
createdAt: string;
|
|
1390
|
+
author: {
|
|
1391
|
+
name: string | null;
|
|
1392
|
+
username: string | null;
|
|
1393
|
+
} | null;
|
|
1394
|
+
media: Array<{
|
|
1395
|
+
type: string;
|
|
1396
|
+
url: string;
|
|
1397
|
+
width: number | null;
|
|
1398
|
+
height: number | null;
|
|
1399
|
+
thumbnailUrl: string | null;
|
|
1400
|
+
hlsUrl: string | null;
|
|
1401
|
+
}>;
|
|
1402
|
+
likeCount: number;
|
|
1403
|
+
repostCount: number;
|
|
1404
|
+
replyCount: number;
|
|
1405
|
+
}
|
|
1406
|
+
/** What every ranked post carries, whichever network it comes from. */
|
|
1407
|
+
export interface ViralItemFields {
|
|
1408
|
+
/** Stable across builds: use it to drop a post already shown. */
|
|
1409
|
+
key: string;
|
|
1410
|
+
label: ViralLabel;
|
|
1411
|
+
score: number;
|
|
1412
|
+
/** 1-based position in this read's ordering, across pages. */
|
|
1413
|
+
rank: number;
|
|
1414
|
+
category: string | null;
|
|
1415
|
+
velocity: ViralItemVelocity;
|
|
1416
|
+
spread: ViralItemSpread;
|
|
1417
|
+
}
|
|
1418
|
+
/** A ranked Minds post; open it at /post/:id. */
|
|
1419
|
+
export interface ViralMindsItem extends ViralItemFields {
|
|
1420
|
+
protocol: 'minds';
|
|
1421
|
+
post: ViralMindsCard;
|
|
1422
|
+
}
|
|
1423
|
+
/** A ranked Bluesky, Fediverse or Nostr post. */
|
|
1424
|
+
export interface ViralSocialItem extends ViralItemFields {
|
|
1425
|
+
protocol: ViralOpenProtocol;
|
|
1426
|
+
post: SocialPost;
|
|
1427
|
+
}
|
|
1428
|
+
/**
|
|
1429
|
+
* One ranked post: check `protocol` and TypeScript narrows `post` (a `ViralMindsCard` for `minds`, a `SocialPost`
|
|
1430
|
+
* otherwise). No item carries a compass value, an author id, a safety flag or its media kind. A visitor's card keeps
|
|
1431
|
+
* its own images and video only where the server cleared them; a withheld one has empty `media` (a `SocialPost` also
|
|
1432
|
+
* has `hasAdditionalContent: true`). Even under `media: 'video'` a visitor's item can arrive without its video, so
|
|
1433
|
+
* test each one with `viralItemHasVideo` before a video player.
|
|
1434
|
+
*/
|
|
1435
|
+
export type ViralItem = ViralMindsItem | ViralSocialItem;
|
|
1436
|
+
/**
|
|
1437
|
+
* Whether the item's own card carries a playable video. Use it on a `media: 'video'` read before a Watch-style
|
|
1438
|
+
* player: a visitor's read can serve an item whose video the server withheld (its card's `media` is then empty).
|
|
1439
|
+
* A video only in a quoted post does not count, as the server never counts a quote's media.
|
|
1440
|
+
*/
|
|
1441
|
+
export declare function viralItemHasVideo(item: ViralItem): boolean;
|
|
1442
|
+
/** A network's trailing pace that velocity is measured against. */
|
|
1443
|
+
export interface ViralBaseline {
|
|
1444
|
+
median: number;
|
|
1445
|
+
p90: number;
|
|
1446
|
+
n: number;
|
|
1447
|
+
source: 'trailing' | 'tick' | 'prior';
|
|
1448
|
+
}
|
|
1449
|
+
export interface ViralProvider {
|
|
1450
|
+
name: string;
|
|
1451
|
+
ok: boolean;
|
|
1452
|
+
lastOkAt: string | null;
|
|
1453
|
+
observedAt: string;
|
|
1454
|
+
}
|
|
1455
|
+
export interface ViralReadMeta {
|
|
1456
|
+
network: ViralNetwork;
|
|
1457
|
+
category_filter: {
|
|
1458
|
+
selected: string;
|
|
1459
|
+
} | null;
|
|
1460
|
+
media_filter: 'video' | null;
|
|
1461
|
+
/** Null unless the read named a filtering `compass`: what Hide politics left out (sides and Mix are not applied here). */
|
|
1462
|
+
compass_filter: CompassFilterMeta | null;
|
|
1463
|
+
safety_filter: {
|
|
1464
|
+
withheld: number;
|
|
1465
|
+
pending: number;
|
|
1466
|
+
};
|
|
1467
|
+
}
|
|
1468
|
+
export interface ViralReadResponse {
|
|
1469
|
+
items: ViralItem[];
|
|
1470
|
+
/** Pass as `cursor` for the next page; null at the end of this build's list. */
|
|
1471
|
+
cursor: string | null;
|
|
1472
|
+
builtAt: string | null;
|
|
1473
|
+
status: ViralReadStatus;
|
|
1474
|
+
window: {
|
|
1475
|
+
primary: '1h';
|
|
1476
|
+
context: '6h';
|
|
1477
|
+
};
|
|
1478
|
+
baselines: Record<string, ViralBaseline>;
|
|
1479
|
+
provenance: {
|
|
1480
|
+
providers: ViralProvider[];
|
|
1481
|
+
};
|
|
1482
|
+
meta: ViralReadMeta;
|
|
1483
|
+
/** True when no further page exists in this build (always on `off` and `building`). */
|
|
1484
|
+
exhausted: boolean;
|
|
1485
|
+
}
|
|
1293
1486
|
/** Read options for callers that hold their own deadline, such as a discovery page. */
|
|
1294
1487
|
export interface SocialReadOptions {
|
|
1295
1488
|
retry?: boolean;
|
|
@@ -1409,6 +1602,14 @@ export declare class ProtocolsResource {
|
|
|
1409
1602
|
getSharedRadarStory(projectId: string, params: {
|
|
1410
1603
|
cluster: string;
|
|
1411
1604
|
}): Promise<SingleResponse<SocialRadarStory>>;
|
|
1605
|
+
/**
|
|
1606
|
+
* The Viral read without a key, for a signed-out visitor: posts whose authors allow wide reach only, and media only
|
|
1607
|
+
* where the server cleared it, so a `media: 'video'` read can carry items whose video was withheld (filter with
|
|
1608
|
+
* `viralItemHasVideo`). `status: 'off'` (no items, never thrown) until the server enables the read for the app;
|
|
1609
|
+
* an API that predates the read answers this path 401 `missing_api_key`, which resolves as off too. Every other
|
|
1610
|
+
* failure throws. Cacheable for a minute by a shared cache.
|
|
1611
|
+
*/
|
|
1612
|
+
getSharedViral(projectId: string, params?: ViralReadParams, options?: SocialReadOptions): Promise<SingleResponse<ViralReadResponse>>;
|
|
1412
1613
|
publicNetworks(): Promise<SingleResponse<SocialNetwork[]>>;
|
|
1413
1614
|
/** Query the live public network. It does not import users or posts. */
|
|
1414
1615
|
searchPublicPosts(params: SearchPublicPostsParams): Promise<SingleResponse<SocialPostSearch>>;
|
|
@@ -1477,6 +1678,12 @@ export declare class ProtocolsResource {
|
|
|
1477
1678
|
getPublicRadarStory(params: {
|
|
1478
1679
|
cluster: string;
|
|
1479
1680
|
}, options?: SocialReadOptions): Promise<SingleResponse<SocialRadarStory>>;
|
|
1681
|
+
/**
|
|
1682
|
+
* The Viral read under the signed-in member's app key (scopes `users:read` and `posts:read`): every network's posts
|
|
1683
|
+
* the build keeps, less the member's own blocks, mutes and hidden posts. Never cached. `status: 'off'` resolves with
|
|
1684
|
+
* no items; a 401 or 403 always throws (a session or key failure is never reported as off).
|
|
1685
|
+
*/
|
|
1686
|
+
getPublicViral(params?: ViralReadParams, options?: SocialReadOptions): Promise<SingleResponse<ViralReadResponse>>;
|
|
1480
1687
|
/** Provider-ranked public accounts with explicit provenance; served from the app's index when a fresh snapshot exists. */
|
|
1481
1688
|
getPublicPopularProfiles(params: {
|
|
1482
1689
|
protocol: string;
|