Config: LIFEOS/USER/CUSTOMIZATIONS/SKILLS/X/Config.md (a fenced yaml block choosing the OAuth 2.0 token store: local, a 0600 file on this machine that x-client.ts refreshes, or cloudflare-kv, a KV namespace that another process such as a scheduled Worker refreshes). Setup, field table and first-run proof: Setup.md. Without it, reads still work on the bearer token, posting falls back to OAuth 1.0a text-only, and every OAuth 2.0 path (media, Articles, bookmarks, delete) stops with an error naming Setup.md.
The bookmark sweep (PullBookmarks) reads three more things from the same directory and the work-tracking config: the bookmark_sweep mapping in that yaml block (issue labels, property label prefix), PREFERENCES.md (relevance profile and what counts as an idea), and the repo for idea-issues (bookmark_sweep.repo, default the repo in LIFEOS/USER/WORK/work_repo.json). Its state lives outside the skill tree in LIFEOS/MEMORY/STATE/X/ (bookmarks-state.json, bookmark-issues.json). The caller outside the skill is Pulse's Synapse module, which reads bookmark-issues.json; the former scheduled sweep that shelled out to Tools/bookmark-issue.ts is retired.
Secrets, all from ~/.claude/.env, loaded by Tools/x-client.ts and never hardcoded (names only):
| Var |
Purpose |
TWITTER_CLIENT_ID |
OAuth 2.0 client ID (authorize + local refresh) |
TWITTER_CLIENT_SECRET |
OAuth 2.0 client secret |
X_BEARER_TOKEN |
App-only bearer token (reads) |
TWITTER_API_KEY, TWITTER_API_KEY_SECRET, X_ACCESS_TOKEN, X_ACCESS_TOKEN_SECRET |
Optional OAuth 1.0a text-only posting fallback |
CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_API_TOKEN, X_TOKENS_KV_NAMESPACE |
Only for token_store: cloudflare-kv |