@recursiv/sdk 0.7.51 → 0.7.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -5
- package/dist/client.d.ts +6 -2
- package/dist/client.js +10 -9
- package/dist/index.d.ts +15 -4
- package/dist/index.js +11 -1
- package/dist/resources/admin.d.ts +13 -0
- package/dist/resources/agents.d.ts +13 -2
- package/dist/resources/agents.js +7 -1
- package/dist/resources/app-subscriptions.d.ts +48 -2
- package/dist/resources/app-subscriptions.js +18 -2
- package/dist/resources/bookmarks.d.ts +172 -0
- package/dist/resources/bookmarks.js +44 -0
- package/dist/resources/boosts.d.ts +23 -1
- package/dist/resources/boosts.js +6 -1
- package/dist/resources/communities.d.ts +12 -2
- package/dist/resources/communities.js +15 -1
- package/dist/resources/curator.d.ts +24 -6
- package/dist/resources/curator.js +2 -1
- package/dist/resources/discovery.d.ts +104 -2
- package/dist/resources/discovery.js +111 -20
- package/dist/resources/invite-codes-admin.d.ts +13 -0
- package/dist/resources/invite-codes.d.ts +10 -0
- package/dist/resources/invite-codes.js +6 -6
- package/dist/resources/link-preview.d.ts +11 -4
- package/dist/resources/link-preview.js +4 -3
- package/dist/resources/notifications.d.ts +26 -0
- package/dist/resources/posts.d.ts +45 -6
- package/dist/resources/posts.js +29 -4
- package/dist/resources/profiles.d.ts +23 -14
- package/dist/resources/profiles.js +18 -5
- package/dist/resources/protocols.d.ts +54 -7
- package/dist/resources/protocols.js +10 -4
- package/dist/resources/search.d.ts +159 -0
- package/dist/resources/search.js +21 -0
- package/dist/types.d.ts +146 -5
- package/llm.md +41 -8
- package/package.json +2 -2
package/llm.md
CHANGED
|
@@ -131,7 +131,7 @@ const { data } = await r.sandbox.execute({ code: '1 + 1', language: 'typescript'
|
|
|
131
131
|
```typescript
|
|
132
132
|
const { data: agent } = await r.agents.create({
|
|
133
133
|
name: 'Home Assistant',
|
|
134
|
-
username
|
|
134
|
+
// username is optional; the server generates a collision-resistant handle
|
|
135
135
|
model: 'anthropic/claude-sonnet-4.6', // see SUPPORTED_MODELS
|
|
136
136
|
system_prompt: 'You help homeowners maintain their property.',
|
|
137
137
|
project_id: projectId, // app-customer flow; works with per-user keys. Use organization_id only for team-owned agents.
|
|
@@ -479,7 +479,7 @@ const { data: rows } = await r.profiles.followers('user_xxx') // each row: is_fo
|
|
|
479
479
|
await r.profiles.mute('user_xxx') // server hides their posts and reposts of them from your feeds
|
|
480
480
|
```
|
|
481
481
|
|
|
482
|
-
Methods: `list`, `me`, `get`, `getByUsername`, `search`, `mentions` (people + agents for an @-picker, follows first), `update`, `follow`, `unfollow`, `followers`, `following`, `followingIds` (your follow set as ids, cursor-paged), `isFollowing`, `related`, `leaderboard`, `block`, `unblock`, `blocked`, `mute`, `unmute`, `muted`
|
|
482
|
+
Methods: `list`, `me`, `get`, `getByUsername`, `search` (ranked by match x ln(1 + followers + min(posts, 100)) over one pool of 200, the same for every page; each row (`ProfileSearchResult`) carries `followers_count`, `posts_count`, `empty` (fold empties under "more handles") and `verified` (the accounts the operator names)), `mentions` (people + agents for an @-picker, follows first), `update`, `follow`, `unfollow`, `followers`, `following`, `followingIds` (your follow set as ids, cursor-paged), `isFollowing`, `related`, `leaderboard`, `block`, `unblock`, `blocked`, `mute`, `unmute`, `muted`
|
|
483
483
|
|
|
484
484
|
---
|
|
485
485
|
|
|
@@ -580,7 +580,36 @@ await r.posts.react(post.id, { type: 'fire' })
|
|
|
580
580
|
const { data: results } = await r.posts.search({ query: 'shipped', community_id: 'comm_xxx' })
|
|
581
581
|
```
|
|
582
582
|
|
|
583
|
-
Methods: `list`, `get`, `create`, `update`, `delete`, `search
|
|
583
|
+
Methods: `list`, `get`, `create`, `update`, `delete`, `search`, `replies(id, { limit, cursor })` (after `replies_next_cursor` on `get`), `react`, `unreact`, `liked`, `bookmarked`, `bookmark`, `unbookmark`
|
|
584
|
+
|
|
585
|
+
`search` ranks relevance x recency x engagement (recency falls as a power of age: this month outranks years ago) through the promotion safety floor; `sort: 'new'` lists newest first. Page with `meta.next_cursor` as `cursor`: later relevance pages are slices of the ranking the first page kept for ten minutes (nothing skipped or repeated while it lives; after that, the page continues past the last post's score boundary), newest-first pages continue by (created_at, id). A cursor is bound to its search and sort: another search's, or the other sort's, is 400 `invalid_cursor`. The first relevance page carries `latest` (up to three of the newest matches not on the page, one per author, for a Latest strip); the last page carries `fall_over.lanes` (`new`, `open_network`, `tag`): draw a way on, not a wall.
|
|
586
|
+
|
|
587
|
+
### r.bookmarks — Synced saves (per member, per app)
|
|
588
|
+
|
|
589
|
+
```typescript
|
|
590
|
+
await r.bookmarks.save({ kind: 'post', post_id: post.id }) // idempotent; a bare repost saves its original (see data.post_id)
|
|
591
|
+
await r.bookmarks.save({ kind: 'social', protocol: 'bluesky', native_id: 'at://did:plc:.../app.bsky.feed.post/...',
|
|
592
|
+
author: { display_name: 'Alice', handle: 'alice.bsky.social' }, snippet: 'hello' })
|
|
593
|
+
await r.bookmarks.save({ kind: 'article', url: 'https://news.example/a', title: 'Headline', outlet: 'Example' })
|
|
594
|
+
const { data: saved, meta } = await r.bookmarks.posts({ limit: 20 }) // = r.posts.bookmarked(); page on meta.next_cursor
|
|
595
|
+
const { data: items } = await r.bookmarks.list({ kind: 'social' }) // open-network posts and articles, as saved snapshots
|
|
596
|
+
const { data: keys } = await r.bookmarks.keys(undefined, { retry: false }) // every key, for a saved-state cache
|
|
597
|
+
const { data: merged } = await r.bookmarks.merge(deviceItems) // ≤200 per call, each with its own saved_at; idempotent
|
|
598
|
+
await r.bookmarks.unsave(key) // or unsave({ postId }); r.posts.bookmark/unbookmark too
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
Newest saved first. Pages can be short while they skip posts the member can no longer read: page on `meta.next_cursor`, not length. Needs the member's own app key (else 403 `project_binding_required`). 503 `bookmarks_unavailable` means the server has no synced bookmarks yet: keep saves on the device (pass `{ retry: false }` to the probe so the 503 is not retried).
|
|
602
|
+
|
|
603
|
+
### r.search
|
|
604
|
+
|
|
605
|
+
```typescript
|
|
606
|
+
// One query, answered as sections. Each has its own time budget; a slow one is listed in `omitted`.
|
|
607
|
+
const { data: answer } = await r.search.query({ q: 'apple', limit: 6 })
|
|
608
|
+
for (const section of answer.order) render(section, answer.sections[section])
|
|
609
|
+
// top_posts, latest_posts, open_network, people, groups, topics, stories, related, trending
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
`trending` joins when the answer is thin or old, so a search never dead-ends. `top_posts` is floored as `r.posts.search`'s relevance page is; `people` rows carry `followers_count`, `posts_count`, `empty` and `verified` (see `r.profiles.search`). Page further with `r.posts.search`. Needs `posts:read`; `people` needs `users:read`, `groups` `communities:read`; `open_network` and `stories` need `users:read` plus the caller's own app key on its network, `trending` that app key. What the key cannot have is in `omitted` (`scope` / `unavailable`).
|
|
584
613
|
|
|
585
614
|
### r.communities
|
|
586
615
|
|
|
@@ -647,9 +676,9 @@ Methods: `list`, `get`, `create`, `delete`
|
|
|
647
676
|
|
|
648
677
|
- `r.settings` — User preferences, sessions, password changes, account deletion. `revokeOtherSessions({ keep_session_token? })` signs out every other device and keeps this key (and the proven session); `revokeAllSessions()` also revokes this key. Accounts with no password confirm `requestEmailChange({ new_email, email_code })` with a code from `requestConfirmationCode('email_change')`. With a project-bound key, `getPreferences()` includes app-scoped `content`; persist changes with `updateContent({ show_nsfw?, autoplay_video?, ai_enabled?, default_feed? })`.
|
|
649
678
|
- `r.billing` — Usage tracking, checkout sessions, portal sessions, metered billing
|
|
650
|
-
- `r.appSubscriptions` — Project-scoped consumer plans: `checkout` (409 `subscription_exists` when the member already pays), `status`, `createPortalSession
|
|
679
|
+
- `r.appSubscriptions` — Project-scoped consumer plans: `checkout` (409 `subscription_exists` when the member already pays), `status`, `createPortalSession({ return_url, flow?: 'cancel' })` (`flow: 'cancel'` opens Stripe's cancel confirmation; 409 `no_subscription` when there is no plan to cancel), `previewTierChange({ tier })`, `changeTier({ tier, proration_date? })` (in-place, prorated, never a second subscription)
|
|
651
680
|
- `r.boosts` — Promoted posts (micro-USD): `create`, `mine`, `balance`, `pause`, `resume`, `cancel`, `recordEvent`; buy credit with `topup({ amount_cents, return_url })` then `confirmTopup(sessionId)` (credits once); admin `listPendingReview`, `review`, `grant`
|
|
652
|
-
- `r.notifications` — The notification stream: `list({ category: 'social' })` (rows carry `actor`, `reaction_type` and a `post_preview` with the viewer's `user_reaction`/`reposted_by_viewer_id` and counts), `unreadCount({ category })` for badges (`const { data } = await r.notifications.unreadCount({ category: 'social' })`), `markAsRead(id)`, `archive(id)` to dismiss, `markAllAsRead(category)` or `markAllAsRead({ category, organization_id })`, plus push token `registerToken`/`unregisterToken`. A push's `data.notificationId` is the row to mark read on tap. DMs are not in the stream.
|
|
681
|
+
- `r.notifications` — The notification stream: `list({ category: 'social' })` (rows carry `actor`, `reaction_type` and a `post_preview` with the viewer's `user_reaction`/`reposted_by_viewer_id` and counts; an `agent_needs_input` row carries `answered`, true once you have written in the agent's thread since it asked, when its title reads "You answered <agent>"), `unreadCount({ category })` for badges (`const { data } = await r.notifications.unreadCount({ category: 'social' })`), `markAsRead(id)`, `archive(id)` to dismiss, `markAllAsRead(category)` or `markAllAsRead({ category, organization_id })`, plus push token `registerToken`/`unregisterToken`. A push's `data.notificationId` is the row to mark read on tap. DMs are not in the stream.
|
|
653
682
|
- `r.uploads` — Legacy media upload URLs (prefer `r.storage` for new projects)
|
|
654
683
|
- `r.github` — GitHub integration (getRepository, getTree, getFileContent, getCommits, createFromTemplate)
|
|
655
684
|
- `r.deployments` — Deployment management (prefer `r.projects.deploy()` for new projects)
|
|
@@ -676,10 +705,10 @@ await r.appSubscriptions.changeTier({ tier: 'pro', proration_date: quote.prorati
|
|
|
676
705
|
- Native follows: `nativeFollowState({ accountId, protocol, nativeId })`, `prepareNativeFollow({ clientMutationId, accountId, protocol, nativeId, action: "follow" | "unfollow" })`, `submitNativeFollow(id, { approve: true, event? })`, `nativeFollowStatus(id)`. Show the acting identity and native following/requested/unfollowed state separately from the Minds feed subscription. Nostr must sign the complete review event without dropping other contacts; stale lists require a fresh review. Keep mutation IDs and reconcile ambiguous outcomes without replay. Requires current follow scopes and API `NATIVE_SOCIAL_FOLLOW_PROJECTS` plus source controls.
|
|
677
706
|
- Native reposts: `nativeRepostState({ accountId, protocol, nativeId })`, `prepareNativeRepost({ clientMutationId, accountId, protocol, nativeId, action: "repost" | "unrepost" })`, `submitNativeRepost(id, { approve: true, event? })`, `nativeRepostStatus(id)`. Requires `NATIVE_SOCIAL_REPOST_PROJECTS`, account capability and explicit approval; Nostr signs the exact review (kind 6 or own kind 5). Unknown attempts only reconcile. Bluesky connections need the new repost scope; ActivityPub uses write:statuses. Native likes: `nativeLikeState({ accountId, protocol, nativeId })`, `prepareNativeLike({ clientMutationId, accountId, protocol, nativeId, action: "like" | "unlike" })`, `submitNativeLike(id, { approve: true, event? })`, and `nativeLikeStatus(id)`. Display the selected account, source and action before approval. Only Nostr supplies `event`: sign the exact public `signing.event` in the review; never transmit a private key. Preserve mutation IDs on ambiguous responses and refresh unknown results instead of resending. Read capabilities before offering actions; old reply-only connections need reconnecting for likes. Nostr results describe the connected relay, not every public relay. The app-specific `NATIVE_SOCIAL_LIKE_PROJECTS` switch and source controls must be enabled on the API.
|
|
678
707
|
- 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, media?, 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, exact `wireText` and every reviewed `media` item with its alt text; connection is not publication approval. `media` takes the same `NativeMediaInput[]` as `prepareNativePost` and needs the account's `publish_media` capability. `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.
|
|
679
|
-
- `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`.
|
|
708
|
+
- `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, inheritEnvironmentApiKey: false }); 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`.
|
|
680
709
|
- 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.
|
|
681
710
|
- Hosted Bluesky account on the app's own PDS: `const { data } = await r.protocols.createHostedIdentity({ protocol: 'atproto', handle: 'alice', email })`. Only the label is sent; `data.handle` carries the domain and `data.password` is returned this once, never stored. Never auto-retried. Either the account is finished or it is removed again. 409 `handle_unavailable` = choose another label; 503 `handle_dns_unavailable`/`handle_unresolvable`/`handle_unverified` = operator setup, retry later unchanged.
|
|
682
|
-
- Anonymous discovery: `const visitor = new Recursiv({ anonymous: true }); const { data } = await visitor.protocols.getSharedTrends(projectId, { protocol: 'nostr', limit: 20, discovery: true })`. Use `sharedNetworks(projectId)` capabilities; open a returned topic with `getSharedTrendFeed(projectId, { protocol: 'bluesky', topic: trend.nativeId, cursor })`. Configured apps only; no account authority or writes. App moderation and per-IP/app rate limits apply. Categories and Jev layouts reuse cached judgments without initiating paid inference; handle partial categories and finite provider windows.
|
|
711
|
+
- Anonymous discovery: `const visitor = new Recursiv({ anonymous: true, inheritEnvironmentApiKey: false }); const { data } = await visitor.protocols.getSharedTrends(projectId, { protocol: 'nostr', limit: 20, discovery: true })`. Use `sharedNetworks(projectId)` capabilities; open a returned topic with `getSharedTrendFeed(projectId, { protocol: 'bluesky', topic: trend.nativeId, cursor })`. Configured apps only; no account authority or writes. App moderation and per-IP/app rate limits apply. Categories and Jev layouts reuse cached judgments without initiating paid inference; handle partial categories and finite provider windows.
|
|
683
712
|
- 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.
|
|
684
713
|
- `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.
|
|
685
714
|
- 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`.
|
|
@@ -790,6 +819,7 @@ The REST API (`/api/v1/*`) supports the `Idempotency-Key` header on POST/PUT/PAT
|
|
|
790
819
|
```typescript
|
|
791
820
|
const r = new Recursiv({
|
|
792
821
|
apiKey: 'sk_live_...', // or set RECURSIV_API_KEY env var
|
|
822
|
+
inheritEnvironmentApiKey: true, // default; false guarantees keyless operation
|
|
793
823
|
baseUrl: 'https://api.recursiv.io/api/v1', // default
|
|
794
824
|
timeout: 30000, // request timeout in ms
|
|
795
825
|
maxRetries: 2, // auto-retry on 429/5xx
|
|
@@ -798,9 +828,12 @@ const r = new Recursiv({
|
|
|
798
828
|
// Zero-arg works when RECURSIV_API_KEY is set
|
|
799
829
|
const r = new Recursiv()
|
|
800
830
|
|
|
801
|
-
// Anonymous sandbox (no API key
|
|
831
|
+
// Anonymous sandbox (no API key required; inherits RECURSIV_API_KEY when set)
|
|
802
832
|
const r = new Recursiv({ anonymous: true })
|
|
803
833
|
|
|
834
|
+
// Guaranteed keyless public client, even inside a credentialed server process
|
|
835
|
+
const visitor = new Recursiv({ anonymous: true, inheritEnvironmentApiKey: false })
|
|
836
|
+
|
|
804
837
|
// Self-hosted
|
|
805
838
|
const r = new Recursiv({ baseUrl: 'https://my-instance.com/api/v1' })
|
|
806
839
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@recursiv/sdk",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.53",
|
|
4
4
|
"description": "TypeScript SDK for the Recursiv API",
|
|
5
5
|
"license": "FSL-1.1-ALv2",
|
|
6
6
|
"type": "module",
|
|
@@ -79,6 +79,6 @@
|
|
|
79
79
|
"build": "tsc",
|
|
80
80
|
"typecheck": "tsc --noEmit",
|
|
81
81
|
"test": "vitest run",
|
|
82
|
-
"test:unit": "vitest run"
|
|
82
|
+
"test:unit": "vitest run && tsc && node ../../scripts/security/verify-sdk-anonymous-package.mjs"
|
|
83
83
|
}
|
|
84
84
|
}
|