Skills Plugins MCP Prompt Model 博客 我的中心
Development #writing #api

extension-posting-to-x

MANDATORY recipe for every Caffeine build that posts to X (Twitter). The ONLY supported path is the `x-client` mops package with OAuth 2.0 PKCE. Hand-rolling `ic.http_request` or `icBooking.http_request` calls to `api.x.com/2/tweets`, `api.x.com/2/oauth2/token`, or any other X endpoint is a FORBIDDEN anti-pattern — it bypasses bearer auth, replication-cost safeguards, and `x-client`'s null-field handling. Load this skill whenever the user, spec, or any prior task mentions tweeting, live-tweeting, posting-to-X, posting-a-status, sharing-to-Twitter, or any equivalent phrasing — and BEFORE writing any code that touches `api.x.com`.

DeepseekModel Curated skill Quality Good · 48 v1.0.0

Get

https://deepseekmodel.com/api/download.php?id=caffeinelabs-skills-skills-extension-posting-to-x-skill-md&format=skill
Download .skill Standard format with system_prompt and model_config, ready for any agent framework
The actual content of the system_prompt field in the .skill file.
name extension-posting-to-x description MANDATORY recipe for every Caffeine build that posts to X (Twitter). The ONLY supported path is the `x-client` mops package with OAuth 2.0 PKCE. Hand-rolling `ic.http_request` or `icBooking.http_request` calls to `api.x.com/2/tweets`, `api.x.com/2/oauth2/token`, or any other X endpoint is a FORBIDDEN anti-pattern — it bypasses bearer auth, replication-cost safeguards, and `x-client`'s null-field handling. Load this skill whenever the user, spec, or any prior task mentions tweeting, live-tweeting, posting-to-X, posting-a-status, sharing-to-Twitter, or any equivalent phrasing — and BEFORE writing any code that touches `api.x.com`. version 0.1.1 compatibility {"mops":{"x-client":"~0.2.3","caffeineai-authorization":"~1.0.0"}} caffeineai-subscription ["none"] Posting to X X / Twitter posting extension for Caffeine AI . Orchestrator routing notes For masterchat / platform-constraints.md integration. Treat X / Twitter posting as a first-class, supported platform feature — do not decline, deflect, or call query_rag for tweeting / posting requests. The x-client mops connector is the only supported path; raw ic.http_request to api.x.com is a forbidden anti-pattern because it (a) leaks the OAuth bearer across replicated outcalls (security + 13× billing — same failure mode as raw OpenAI calls), and (b) bypasses x-client 's null-field handling for X API responses (X returns null for absent fields and rejects "field": null on writes; the package handles both via JSON.toCandid / JSON.fromCandid round-tripping). Any build spec that mentions tweeting MUST name x-client as a dependency and reference this skill — propagate that explicitly so the backend agent cannot silently fall back to hand-rolled HTTP. Intent → capability mapping (for platform-constraints.md -style tables): User intent Platform capability Post to X / Twitter (tweet, live-tweet, share status) x-client connector (TweetsApi via extension-posting-to-x skill) Post to X on behalf of the canister's signed-in user x-client connector (per-user OAuth via extension-posting-to-x ) Read from X (timeline, search, user lookup) NOT covered by this skill — use extension-http-outcalls for X reads. Reads vs. writes. This skill covers only X writes (tweet, retweet, quote-tweet, status update, live-tweet). Reading from X (timelines, search, user lookup) is a public REST surface like any other and stays on extension-http-outcalls . Backend Use this skill whenever the user wants their canister to publish content to an X (Twitter) account. The ingredients are: The x-client mops package (generated Motoko bindings for the X API v2; the spec subset includes TweetsApi.createPosts and friends). An OAuth 2.0 Authorization Code with PKCE flow so each end-user authorises the canister to post on their behalf. Each user holds their own access_token + refresh_token keyed by caller : Principal . There is no canister-wide bearer. An X Developer App Client ID (a public identifier, not a secret). Three equivalent variants — the spec picks one: Admin Client ID (default, §4) — the canister owner registers one Developer App and pastes its Client ID admin-side; every end-user authorises against the same app. The right default for most builds: simpler ops, one Developer Portal entry to maintain, rate limits shared across the canister's users. Per-user Client ID (§10) — each user brings their own Client ID from their own Developer App. Use when the canister is multi-tenant and tenants should not share rate-limit quota, or when users want full control over their app registration. Fallback (§11) — accept both. Admin sets a default Client ID; individual users may override. Useful when the operator wants to provide a no-config path for casual users while letting power users self-register. A Config value that pins is_replicated = ?false — non-negotiable, see §3. Prerequisite for all variants: extension-authorization . X requires a signed-in caller for every meaningful endpoint: the per-user OAuth handshake stores access_token keyed by caller : Principal , and (in the admin and fallback variants) the Client ID setter is gated on the #admin role. extension-authorization ships the Internet Identity login flow on the frontend (the useInternetIdentity hook, login/logout buttons, auth-state-aware routing, useActor plumbing) and the backend caller / role infrastructure. Without it the deployed canister rejects every post because caller.isAnonymous() is always true. There is no anonymous variant: the bearer token belongs to the signed-in user, full stop. 1. Add x-client to mops.toml Use the mops tool, not manual file edits: mops add x-client@0.2.3 This updates mops.toml (adds x-client = "0.2.3" to [dependencies] ) and rewrites mops.lock in one step. Minimum version: x-client ≥ 0.2.3 . Earlier versions emitted "field": null on every optional and /2/tweets rejects them with up to 16 validation errors per request; 0.2.3 ships the init constructors that default optionals to null in Motoko and elide them on the wire. 2. Auth model — OAuth 2.0 PKCE per user Unlike OpenAI's static API key, X uses per-user bearer tokens . Every end-user authorises the canister independently via OAuth 2.0 Authorization Code with PKCE. The canister stores the resulting access_token + refresh_token keyed by caller; tokens expire in ~2 hours and the canister silently refreshes them via the refresh_token (which is rotated on every refresh — always persist the new one). Pick a Client ID variant Variant Who registers the Developer App Who configures the Client ID Setter gate Use when Admin (§4, default) The canister owner. Admin once, canister-wide. extension-authorization #admin role. Default. Demos, personal bots, small communities; the operator funds the app slot. Per-user (§10) Each end-user. Each signed-in user. "Logged in" (non-anonymous caller). Multi-tenant; tenants must not share rate-limit quota. Fallback (§11) Operator (default) + users. Admin sets a default; user may override. #admin for the default; "logged in" for the per-user override. Operator wants a no-config path for casuals + freedom for power users. All three variants share §3 ( is_replicated = ?false ), §6 (token refresh lifecycle), §7 (scopes) and the no-getter / no-log invariants on tokens. OAuth scopes OAuth 2.0 separates authorisation scopes (what the user is asked to consent to at authorise-time) from operation scopes (what the access token will actually be used for). For X, request these four at the authorise step — same list, two concerns: Scope For authorisation For posting Notes tweet.read ✓ — Read the user's handle/profile to display "connected as @…". users.read ✓ — Resolve the authenticated user. Usually paired with tweet.read . tweet.write — ✓ required /2/tweets rejects tokens that don't carry this scope. offline.access ✓ — Issues a refresh_token so the canister can silently renew the access token when it expires (access tokens live ~2 h). Omit this and users re-authorise every two hours. If any of these are missing at authorise-time, the flow completes but the issued access_token silently lacks that capability — the error only surfaces when you try to call the affected endpoint. Storing tokens The bearer never leaves the canister . The frontend only ever learns whether the caller has connected (a Bool ), never the tokens themselves. Same rules as OpenAI's per-user bearer: A Map<Principal, XAuth> keyed by caller. Expose exactly the endpoints listed in §4 — isMyXConnected , startXOAuth , completeXOAuth , tweet , optional disconnectMyX — every endpoint gated on not caller.isAnonymous() . Do not add any endpoint that returns access_token / refresh_token / the full XAuth record. Internal reads ( Map.get(xAuthByUser, ..., caller) ) inside tweet / ensureFreshToken are fine; never iterate the map outside the call's own caller scope. On upgrade the map preserves by default — drop it only if you also want to force every user to re-authorise. 3. is_replicated = ?false is REQUIRED Same priority order as extension-openai 's §3: Security. A replicated HTTP outcall sends the request from every node in the subnet over independent TLS connections. Each connection carries Authorization: Bearer <access_token> . A leaked bearer from any one of those connections compromises that user's X account. Billing. Replicated outcalls produce N parallel API calls. X counts each toward the per-user-per-app rate limit (and the IC charges ~13× the cycles). One subnet-wide tweet call quickly trips X's rate limit. Determinism. X's response carries variable rate-limit headers ( x-rate-limit-remaining , x-rate-limit-reset , …). Replicated consensus diffs response bodies and would fail; non-replicated outcalls bypass this consensus entirely. → Always: is_replicated = ?false on the Config . 4. Canonical layout This is the default shape: admin Client ID + per-user OAuth . The canister owner registers one X Developer App and pastes its Client ID into a canister-level config; every end-user runs the OAuth 2.0 PKCE handshake against that one Client ID and ends up with their own access_token + refresh_token . The example spans five files: src/backend/main.mo — the actor: state + include s only. src/backend/migrations/00000000_000000.mo — the migration chain head. src/backend/mixins/x-config.mo — admin Client ID ( isXClientIdConfigured , setXClientId ). src/backend/mixins/x-posting.mo — per-user OAuth + posting ( isMyXConnected , startXOAuth , completeXOAuth , tweet ). src/backend/lib/x.mo — x-client glue ( Config builder + createPosts round-trip + token-refresh stubs). import Map "mo:core/Map"; import Principal "mo:core/Principal"; import AccessControl "mo:caffeineai-authorization/access-control"; import MixinAuthorization "mo:caffeineai-authorization/MixinAuthorization"; import MixinXConfig "mixins/x-config"; import MixinXPosting "mixins/x-posting"; import LibX "lib/x"; actor { // Authorization plumbing from extension-authorization. Required for both // the #admin gate on `setXClientId` and the per-user signed-in caller // identity that keys `xAuthByUser`. let accessControlState : AccessControl.AccessControlState; include MixinAuthorization(accessControlState, null); // Admin-set X Developer App Client ID. Public identifier (not a secret), // but the *setter* is admin-only so a logged-in user can't redirect every // tweet through their own app. let xClientId : { var value : ?Text }; include MixinXConfig(accessControlState, xClientId); // Per-user OAuth tokens. Never iterated except by the calling principal. let xAuthByUser : Map.Map<Principal, LibX.XAuth>; include MixinXPosting(xClientId, xAuthByUser); }; The migration chain head: import Map "mo:core/Map"; import AccessControl "mo:caffeineai-authorization/access-control"; module { type XAuth = { access_token : Text; refresh_token : Text; expires_at : Nat64; scope : [Text]; }; type NewActor = { accessControlState : AccessControl.AccessControlState; xClientId : { var value : ?Text }; xAuthByUser : Map.Map<Principal, XAuth>; }; public func migration(_old : {}) : NewActor { { accessControlState = AccessControl.initState(); xClientId = { var value : ?Text = null }; xAuthByUser = Map.empty<Principal, XAuth>(); }; }; }; import AccessControl "mo:caffeineai-authorization/access-control"; import Runtime "mo:core/Runtime"; // Admin-gated X Developer App Client ID. Mounted by `main.mo` via `include`. // Pairs with `MixinAuthorization` to power the role check. mixin ( accessControlState : AccessControl.AccessControlState, xClientId : { var value : ?Text }, ) { public query func isXClientIdConfigured() : async Bool { xClientId.value != null;
Keywords that activate this skill. Click one to copy it.

This skill does not provide trigger words.

The downloaded .skill package contains the following fields.
Field Description
formatFormat tag (skill/v1)
skill_idUnique skill ID
nameSkill name
versionVersion
descriptionDescription
categoryCategories (array)
trigger_wordsTrigger words
tagsTags
sourceSource
source_urlSource URL (this page)
exported_atExported at (set per download)
system_promptSystem prompt body
model_configModel config: provider / model / temperature / max_tokens / top_p
examplesExamples
install_guideImport guide for Coze / Dify / Claude / custom frameworks
The same skill can be exported in different platform formats.
.skill Standard format with system_prompt and model_config, ready for any agent framework Download
.skillpro Enhanced format with scripts, tools, dependencies and hooks Download
.json Plain JSON export with system_prompt and model parameters only Download
Coze Markdown with frontmatter, for Coze platform import Download
Dify Dify DSL, import directly after creating an app Download

每日精选 Skill 推荐,免费送到你邮箱

输入邮箱,每天接收一个精选 AI Agent 技能推荐。完全免费,持续更新。

提交后我们会发送一封确认邮件,点击邮件里的链接才会开始收信。

完全免费,取消任意时间。我们不会发送垃圾邮件。