@hidden-leaf/x-skill 1.0.0
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/.env.example +50 -0
- package/CLAUDE.snippet.md +34 -0
- package/README.md +220 -0
- package/SKILL.md +265 -0
- package/dist/cache/store.d.ts +48 -0
- package/dist/cache/store.d.ts.map +1 -0
- package/dist/cache/store.js +381 -0
- package/dist/cache/store.js.map +1 -0
- package/dist/clients/types.d.ts +217 -0
- package/dist/clients/types.d.ts.map +1 -0
- package/dist/clients/types.js +77 -0
- package/dist/clients/types.js.map +1 -0
- package/dist/clients/x-client.d.ts +111 -0
- package/dist/clients/x-client.d.ts.map +1 -0
- package/dist/clients/x-client.js +421 -0
- package/dist/clients/x-client.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +52 -0
- package/dist/index.js.map +1 -0
- package/dist/skills/bookmarks/index.d.ts +79 -0
- package/dist/skills/bookmarks/index.d.ts.map +1 -0
- package/dist/skills/bookmarks/index.js +288 -0
- package/dist/skills/bookmarks/index.js.map +1 -0
- package/dist/skills/bookmarks/synthesize.d.ts +40 -0
- package/dist/skills/bookmarks/synthesize.d.ts.map +1 -0
- package/dist/skills/bookmarks/synthesize.js +164 -0
- package/dist/skills/bookmarks/synthesize.js.map +1 -0
- package/dist/skills/bookmarks/types.d.ts +71 -0
- package/dist/skills/bookmarks/types.d.ts.map +1 -0
- package/dist/skills/bookmarks/types.js +6 -0
- package/dist/skills/bookmarks/types.js.map +1 -0
- package/dist/utils/logger.d.ts +10 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +43 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +68 -0
- package/scripts/cache-report.ts +25 -0
- package/scripts/create-atlsk-ticket.ts +68 -0
- package/scripts/create-deep-dive-ticket.ts +66 -0
- package/scripts/create-jira-project.ts +31 -0
- package/scripts/create-launch-and-roadmap.ts +200 -0
- package/scripts/create-next-session.ts +71 -0
- package/scripts/create-roadmap.ts +150 -0
- package/scripts/debug-api.ts +45 -0
- package/scripts/debug-folder.ts +37 -0
- package/scripts/jira-close-v1-tickets.ts +83 -0
- package/scripts/jira-v1-close-and-post-v1.ts +272 -0
- package/scripts/oauth-flow.ts +216 -0
- package/scripts/postinstall.js +112 -0
- package/scripts/sync-test.ts +36 -0
- package/scripts/test-refresh-forced.ts +29 -0
- package/scripts/test-refresh.ts +25 -0
package/.env.example
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# X API Authentication — OAuth 2.0 User Context
|
|
3
|
+
# ============================================================================
|
|
4
|
+
# Bookmarks require OAuth 2.0 User Context (not App-only Bearer token).
|
|
5
|
+
# Set up User Authentication in your app at: https://developer.x.com/en/portal/dashboard
|
|
6
|
+
#
|
|
7
|
+
# Step 1: Save your app credentials (shown once at app creation)
|
|
8
|
+
# Step 2: Set up User Authentication → Read permissions, Web App, PKCE
|
|
9
|
+
# Step 3: Run the OAuth flow script to get your User Access Token:
|
|
10
|
+
# npx tsx scripts/oauth-flow.ts
|
|
11
|
+
|
|
12
|
+
# App credentials (from app creation screen — shown once, save them!)
|
|
13
|
+
X_CONSUMER_KEY=
|
|
14
|
+
X_CONSUMER_SECRET=
|
|
15
|
+
|
|
16
|
+
# OAuth 2.0 User Access Token (generated via OAuth flow script)
|
|
17
|
+
# This is the token that actually reads YOUR bookmarks as YOU.
|
|
18
|
+
# Expires after ~2 hours — use the refresh token to get a new one.
|
|
19
|
+
X_USER_ACCESS_TOKEN=
|
|
20
|
+
|
|
21
|
+
# Refresh token — used to get a new access token when the current one expires.
|
|
22
|
+
# Long-lived. Run `npx tsx scripts/oauth-flow.ts` again if both expire.
|
|
23
|
+
X_REFRESH_TOKEN=
|
|
24
|
+
|
|
25
|
+
# Your numeric X user ID (printed by the OAuth flow script)
|
|
26
|
+
X_USER_ID=
|
|
27
|
+
|
|
28
|
+
# OAuth callback URL (must match what you set in app settings)
|
|
29
|
+
X_CALLBACK_URL=http://localhost:3000/callback
|
|
30
|
+
|
|
31
|
+
# ============================================================================
|
|
32
|
+
# Local Cache
|
|
33
|
+
# ============================================================================
|
|
34
|
+
# Path to SQLite database (default: ~/.x-skill/bookmarks.db)
|
|
35
|
+
# X_CACHE_DB_PATH=
|
|
36
|
+
|
|
37
|
+
# ============================================================================
|
|
38
|
+
# Client Options
|
|
39
|
+
# ============================================================================
|
|
40
|
+
# Max retries on 5xx/network errors (default: 3)
|
|
41
|
+
# X_MAX_RETRIES=3
|
|
42
|
+
|
|
43
|
+
# Request timeout in ms (default: 30000)
|
|
44
|
+
# X_TIMEOUT=30000
|
|
45
|
+
|
|
46
|
+
# ============================================================================
|
|
47
|
+
# Logging
|
|
48
|
+
# ============================================================================
|
|
49
|
+
# Log level: debug, info, warn, error (default: info)
|
|
50
|
+
LOG_LEVEL=info
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# X Bookmark Intelligence (Library-Based Skill)
|
|
2
|
+
|
|
3
|
+
This project uses `@hidden-leaf/x-skill` — a TypeScript library for X (Twitter) bookmark intelligence.
|
|
4
|
+
This is NOT an MCP tool or registered skill. To use it, write a TypeScript script and execute it with `npx tsx <script>.ts`.
|
|
5
|
+
|
|
6
|
+
## Setup
|
|
7
|
+
- Credentials are in `.env` (see `node_modules/@hidden-leaf/x-skill/.env.example`)
|
|
8
|
+
- Required: `X_BEARER_TOKEN`, `X_USER_ID`
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
When the user asks about X bookmarks, bookmark folders, research briefs from saved posts, or wants to analyze their X content — use this skill.
|
|
12
|
+
Read node_modules/@hidden-leaf/x-skill/SKILL.md for the full API before using.
|
|
13
|
+
|
|
14
|
+
## How to Use
|
|
15
|
+
Write a .ts script, then run it with `npx tsx <script>.ts`:
|
|
16
|
+
```typescript
|
|
17
|
+
import { createBookmarksSkillFromEnv } from '@hidden-leaf/x-skill';
|
|
18
|
+
|
|
19
|
+
const skill = createBookmarksSkillFromEnv();
|
|
20
|
+
|
|
21
|
+
// List folders
|
|
22
|
+
const { folders } = await skill.listFolders();
|
|
23
|
+
|
|
24
|
+
// Fetch posts from a folder
|
|
25
|
+
const result = await skill.fetchByFolderName('Robotics');
|
|
26
|
+
|
|
27
|
+
// Generate a research brief
|
|
28
|
+
const { brief } = await skill.brief({
|
|
29
|
+
folderName: 'AI MEDICINE',
|
|
30
|
+
hlnContext: ['Applied AI Studio'],
|
|
31
|
+
});
|
|
32
|
+
console.log(brief.content);
|
|
33
|
+
```
|
|
34
|
+
Then execute: `npx tsx <script>.ts`
|
package/README.md
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# @hidden-leaf/x-skill
|
|
2
|
+
|
|
3
|
+
**Turn your X bookmarks into structured research intelligence.**
|
|
4
|
+
|
|
5
|
+
[](https://github.com/Hidden-Leaf-Networks/x-skill/actions/workflows/ci.yml)
|
|
6
|
+
[](https://www.npmjs.com/package/@hidden-leaf/x-skill)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
[](https://nodejs.org)
|
|
9
|
+
[](https://www.typescriptlang.org/)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
A TypeScript skill for [Claude Code](https://claude.ai/claude-code) that syncs your X (Twitter) bookmark folders into a local SQLite cache and synthesizes them into actionable research briefs. Sync once, read forever.
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
X Bookmarks --> Sync (API) --> SQLite Cache --> Research Briefs
|
|
17
|
+
folders pennies local/free themes, voices,
|
|
18
|
+
posts per sync offline signal, actions
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Why
|
|
22
|
+
|
|
23
|
+
Your X bookmarks are a curated research goldmine — AI papers, robotics threads, crypto analysis, medicine breakthroughs — organized into folders. But they're trapped in the app with no way to search, analyze, or act on them programmatically.
|
|
24
|
+
|
|
25
|
+
x-skill fixes that. Pull your folders, cache locally, synthesize into structured intelligence. The cache-first architecture means you pay for the sync (~$5-15/month), but every read, brief, and analysis after that is **completely free**.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install @hidden-leaf/x-skill
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Setup
|
|
34
|
+
|
|
35
|
+
### 1. Get X API Access
|
|
36
|
+
|
|
37
|
+
Sign up at [developer.x.com](https://developer.x.com) for pay-per-use API access.
|
|
38
|
+
|
|
39
|
+
Create an app with these OAuth 2.0 scopes:
|
|
40
|
+
- `bookmark.read`
|
|
41
|
+
- `tweet.read`
|
|
42
|
+
- `users.read`
|
|
43
|
+
- `offline.access`
|
|
44
|
+
|
|
45
|
+
### 2. Run the OAuth Flow
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npx tsx node_modules/@hidden-leaf/x-skill/scripts/oauth-flow.ts
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
This opens your browser, authenticates via PKCE, and prints your credentials.
|
|
52
|
+
|
|
53
|
+
### 3. Configure Environment
|
|
54
|
+
|
|
55
|
+
Add the output to your `.env`:
|
|
56
|
+
|
|
57
|
+
```env
|
|
58
|
+
X_CONSUMER_KEY=your-consumer-key
|
|
59
|
+
X_CONSUMER_SECRET=your-consumer-secret
|
|
60
|
+
X_USER_ACCESS_TOKEN=your-access-token
|
|
61
|
+
X_REFRESH_TOKEN=your-refresh-token
|
|
62
|
+
X_USER_ID=your-numeric-user-id
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Tokens auto-refresh on expiry — set it up once, runs forever.
|
|
66
|
+
|
|
67
|
+
## Quick Start
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { createBookmarksSkillFromEnv } from '@hidden-leaf/x-skill';
|
|
71
|
+
|
|
72
|
+
const skill = createBookmarksSkillFromEnv();
|
|
73
|
+
|
|
74
|
+
// Sync bookmarks from X API into local cache (costs money)
|
|
75
|
+
const sync = await skill.syncAll();
|
|
76
|
+
console.log(`Synced ${sync.totalTweets} posts across ${sync.totalFolders} folders`);
|
|
77
|
+
|
|
78
|
+
// List your bookmark folders (free — reads from cache)
|
|
79
|
+
const { folders } = skill.listFolders();
|
|
80
|
+
folders.forEach(f => console.log(`${f.name} (${f.tweet_count} posts)`));
|
|
81
|
+
|
|
82
|
+
// Fetch posts from a folder (free)
|
|
83
|
+
const result = skill.fetchByFolderName('Robotics');
|
|
84
|
+
console.log(`${result.totalTweets} posts from ${result.uniqueAuthors} authors`);
|
|
85
|
+
|
|
86
|
+
// Generate a research brief (free)
|
|
87
|
+
const { brief } = await skill.brief({
|
|
88
|
+
folderName: 'AI Medicine',
|
|
89
|
+
hlnContext: ['Applied AI Studio'],
|
|
90
|
+
});
|
|
91
|
+
console.log(brief.content);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Run with: `npx tsx script.ts`
|
|
95
|
+
|
|
96
|
+
## API Reference
|
|
97
|
+
|
|
98
|
+
### Sync Commands (hit X API — cost money)
|
|
99
|
+
|
|
100
|
+
| Method | Description |
|
|
101
|
+
|--------|-------------|
|
|
102
|
+
| `syncAll()` | Pull all bookmark folders and tweets into cache |
|
|
103
|
+
| `syncFolder(name)` | Sync a single folder by name (case-insensitive) |
|
|
104
|
+
|
|
105
|
+
### Read Commands (from cache — free)
|
|
106
|
+
|
|
107
|
+
| Method | Description |
|
|
108
|
+
|--------|-------------|
|
|
109
|
+
| `listFolders()` | List all cached bookmark folders |
|
|
110
|
+
| `fetchByFolderName(name)` | Get enriched bookmarks from a folder |
|
|
111
|
+
| `fetchByFolderId(id)` | Get enriched bookmarks by folder ID |
|
|
112
|
+
| `fetchAll()` | Get all cached bookmarks |
|
|
113
|
+
| `stats()` | Cache statistics (folder/tweet/user counts, last sync) |
|
|
114
|
+
| `close()` | Close database connection |
|
|
115
|
+
|
|
116
|
+
### Synthesis (from cache — free)
|
|
117
|
+
|
|
118
|
+
| Method | Description |
|
|
119
|
+
|--------|-------------|
|
|
120
|
+
| `brief(options?)` | Generate a research brief with themes, notable voices, signal scoring |
|
|
121
|
+
| `buildBriefPrompt(bookmarks, options)` | Build the raw synthesis prompt for custom use |
|
|
122
|
+
|
|
123
|
+
### Brief Options
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
interface BriefOptions {
|
|
127
|
+
folderName?: string; // Scope to a specific folder
|
|
128
|
+
folderId?: string; // Or use folder ID directly
|
|
129
|
+
maxTweets?: number; // Max tweets to analyze (default: 50)
|
|
130
|
+
customPrompt?: string; // Append custom instructions
|
|
131
|
+
hlnContext?: string[]; // Relate to specific ventures/initiatives
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Research Brief Output
|
|
136
|
+
|
|
137
|
+
Each brief includes:
|
|
138
|
+
- **Key Themes** — 3-7 major trends with evidence citations
|
|
139
|
+
- **Notable Voices** — Most influential accounts, ranked by engagement
|
|
140
|
+
- **Signal vs. Noise** — Quality score (1-10), high-signal vs low-signal posts
|
|
141
|
+
- **Actionable Intelligence** — What to act on, emerging opportunities and risks
|
|
142
|
+
- **HLN Relevance** — How findings connect to your ventures and initiatives
|
|
143
|
+
|
|
144
|
+
## Architecture
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
@hidden-leaf/x-skill
|
|
148
|
+
├── src/
|
|
149
|
+
│ ├── clients/
|
|
150
|
+
│ │ ├── x-client.ts # X API v2 HTTP client, OAuth 2.0, auto-refresh
|
|
151
|
+
│ │ └── types.ts # Full X API v2 type definitions, error classes
|
|
152
|
+
│ ├── cache/
|
|
153
|
+
│ │ └── store.ts # SQLite cache — WAL mode, upserts, 5-table schema
|
|
154
|
+
│ ├── skills/
|
|
155
|
+
│ │ └── bookmarks/
|
|
156
|
+
│ │ ├── index.ts # BookmarksSkill — sync, list, fetch, brief
|
|
157
|
+
│ │ ├── synthesize.ts # Prompt builder, theme/voice extraction
|
|
158
|
+
│ │ └── types.ts # EnrichedBookmark, ResearchBrief, outputs
|
|
159
|
+
│ └── utils/
|
|
160
|
+
│ └── logger.ts # Structured logger with levels
|
|
161
|
+
├── scripts/
|
|
162
|
+
│ └── oauth-flow.ts # OAuth 2.0 PKCE setup flow
|
|
163
|
+
├── SKILL.md # Full API reference for Claude Code
|
|
164
|
+
└── CLAUDE.snippet.md # Auto-injected into consuming projects
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Design Principles
|
|
168
|
+
|
|
169
|
+
- **Sync-then-read** — API calls only during sync. All reads hit local SQLite. You only pay when you pull new data
|
|
170
|
+
- **Cache-first** — SQLite with WAL mode for fast reads and crash-safe writes. Cross-session data access for downstream consumers
|
|
171
|
+
- **No Claude API dependency** — Brief synthesis builds prompts for Claude Code to execute inline. No separate API key required
|
|
172
|
+
- **Factory pattern** — `create*FromEnv()` functions for all major classes. Configuration via environment variables
|
|
173
|
+
- **Minimal API calls** — Smart hydration: fetch folder IDs (cheap), then hydrate only missing tweets via batch lookup (100 per call)
|
|
174
|
+
|
|
175
|
+
## Cost
|
|
176
|
+
|
|
177
|
+
X API uses pay-per-use pricing (launched Feb 2026). Same post requested within a 24h UTC window counts as a single charge.
|
|
178
|
+
|
|
179
|
+
| Usage Pattern | Est. Monthly Cost |
|
|
180
|
+
|---------------|-------------------|
|
|
181
|
+
| Weekly sync, 5 folders | ~$5 |
|
|
182
|
+
| Daily sync, 10 folders | ~$15 |
|
|
183
|
+
| Multiple daily syncs, 20+ folders | ~$25-50 |
|
|
184
|
+
|
|
185
|
+
**After sync, everything is free.** Briefs, fetches, searches, exports — zero API calls.
|
|
186
|
+
|
|
187
|
+
## Development
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
git clone https://github.com/Hidden-Leaf-Networks/x-skill.git
|
|
191
|
+
cd x-skill
|
|
192
|
+
npm install
|
|
193
|
+
|
|
194
|
+
npm run build # Compile TypeScript
|
|
195
|
+
npm run lint # ESLint
|
|
196
|
+
npm test # Jest test suite
|
|
197
|
+
npm run watch # TypeScript watch mode
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Downstream Integration
|
|
201
|
+
|
|
202
|
+
x-skill is designed as an intelligence primitive that feeds into larger systems:
|
|
203
|
+
|
|
204
|
+
- **Jira/Confluence** — Create research tickets from briefs via `@hidden-leaf/atlassian-skill`
|
|
205
|
+
- **Training data** — Feed curated posts into fine-tuning pipelines
|
|
206
|
+
- **Planning context** — Ambient research input for AI assistants
|
|
207
|
+
- **Client research** — Structured deliverables from bookmark analysis
|
|
208
|
+
|
|
209
|
+
## Roadmap
|
|
210
|
+
|
|
211
|
+
| Version | Focus |
|
|
212
|
+
|---------|-------|
|
|
213
|
+
| **1.0.0** | Bookmark intelligence — sync, cache, list, fetch, brief |
|
|
214
|
+
| **1.1** | OAuth flow polish, error recovery improvements |
|
|
215
|
+
| **2.0** | Search skill, thread parsing, profile management |
|
|
216
|
+
| **3.0** | Publish, schedule, reply (write operations) |
|
|
217
|
+
|
|
218
|
+
## License
|
|
219
|
+
|
|
220
|
+
MIT — [Hidden Leaf Networks](https://github.com/Hidden-Leaf-Networks)
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# @hidden-leaf/x-skill — Claude Code Skill Reference
|
|
2
|
+
|
|
3
|
+
> **When to use this skill:** When the user mentions X bookmarks, Twitter bookmarks, bookmark folders, research briefs from X, or wants to analyze saved posts from X/Twitter.
|
|
4
|
+
|
|
5
|
+
## Architecture: Sync + Cache
|
|
6
|
+
|
|
7
|
+
This skill uses a **sync-then-read** pattern:
|
|
8
|
+
- **`sync`** pulls data from the X API into a local SQLite cache (`~/.x-skill/bookmarks.db`). This is the only operation that costs money.
|
|
9
|
+
- **`list`**, **`fetch`**, **`brief`**, **`stats`** all read from the local cache — free, instant, offline-capable.
|
|
10
|
+
|
|
11
|
+
Always run `sync` first before reading. After that, reads are unlimited.
|
|
12
|
+
|
|
13
|
+
## Setup
|
|
14
|
+
|
|
15
|
+
### Required Environment Variables
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
X_BEARER_TOKEN=<OAuth 2.0 User Context Bearer token>
|
|
19
|
+
X_USER_ID=<your numeric X user ID>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Optional
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
X_CACHE_DB_PATH=<custom path to SQLite DB, default: ~/.x-skill/bookmarks.db>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### How to Get Credentials
|
|
29
|
+
|
|
30
|
+
1. **X Developer Account:** Sign up at [developer.x.com](https://developer.x.com) (pay-per-use pricing, ~$5-15/month for bookmark reads)
|
|
31
|
+
2. **Bearer Token:** Create an app in the Developer Portal → generate OAuth 2.0 User Context token with `bookmark.read`, `tweet.read`, `users.read` scopes
|
|
32
|
+
3. **User ID:** Use [tweeterid.com](https://tweeterid.com) or call `GET /2/users/me` with your token
|
|
33
|
+
|
|
34
|
+
## API Reference
|
|
35
|
+
|
|
36
|
+
### BookmarksSkill
|
|
37
|
+
|
|
38
|
+
The primary v1 skill. All methods use the `BookmarksSkill` class.
|
|
39
|
+
|
|
40
|
+
#### Create Instance
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { createBookmarksSkillFromEnv } from '@hidden-leaf/x-skill';
|
|
44
|
+
const bookmarks = createBookmarksSkillFromEnv();
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
#### Sync All (hits X API — costs money)
|
|
48
|
+
|
|
49
|
+
Pull all bookmark folders and their tweets into the local cache.
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
const result = await bookmarks.syncAll();
|
|
53
|
+
// result.totalFolders: number
|
|
54
|
+
// result.totalTweets: number
|
|
55
|
+
// result.folders: [{ name, id, tweetCount }]
|
|
56
|
+
// result.syncedAt: ISO string
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
#### Sync Single Folder (hits X API — costs money)
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
const result = await bookmarks.syncFolder('Robotics');
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
#### List Folders (from cache — free)
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
const { folders, totalFolders } = bookmarks.listFolders();
|
|
69
|
+
// folders: [{ id, name, description?, tweet_count? }]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
#### Fetch by Folder Name (from cache — free)
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
const result = bookmarks.fetchByFolderName('Robotics');
|
|
76
|
+
// result.bookmarks: [{ tweet, author, formatted }]
|
|
77
|
+
// result.totalTweets: number
|
|
78
|
+
// result.uniqueAuthors: number
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
#### Fetch by Folder ID (from cache — free)
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
const result = bookmarks.fetchByFolderId('folder-id-here');
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
#### Fetch All Bookmarks (from cache — free)
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
const result = bookmarks.fetchAll();
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
#### Generate Research Brief (from cache — free)
|
|
94
|
+
|
|
95
|
+
Synthesize a research brief from cached bookmarks. Builds a structured prompt that Claude analyzes.
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
const { brief, sourceBookmarks } = await bookmarks.brief({
|
|
99
|
+
folderName: 'Robotics',
|
|
100
|
+
maxTweets: 50,
|
|
101
|
+
hlnContext: ['HLOS', 'Asimov', 'Applied AI Studio'],
|
|
102
|
+
customPrompt: 'Focus on humanoid robotics breakthroughs',
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// brief.topic: 'Robotics'
|
|
106
|
+
// brief.content: <synthesis prompt for Claude>
|
|
107
|
+
// brief.notableVoices: ['@user1', '@user2', ...]
|
|
108
|
+
// brief.themes: ['humanoid robotics', '#AI', ...]
|
|
109
|
+
// brief.hlnRelevance: ['HLOS', 'Asimov', 'Applied AI Studio']
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
#### Cache Stats (free)
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
const stats = bookmarks.stats();
|
|
116
|
+
// { folders: 8, tweets: 342, users: 89, lastSync: '2026-03-27T...' }
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
#### Cleanup
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
bookmarks.close(); // Close SQLite connection
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### XClient (Low-Level)
|
|
126
|
+
|
|
127
|
+
Direct X API v2 access — bypasses cache, hits API directly.
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
import { createXClientFromEnv } from '@hidden-leaf/x-skill';
|
|
131
|
+
const x = createXClientFromEnv();
|
|
132
|
+
|
|
133
|
+
// Get bookmarks (paginated)
|
|
134
|
+
const page = await x.getBookmarks({ max_results: 100 });
|
|
135
|
+
|
|
136
|
+
// Get all bookmarks (auto-paginate)
|
|
137
|
+
const { tweets, users } = await x.getAllBookmarks();
|
|
138
|
+
|
|
139
|
+
// List bookmark folders
|
|
140
|
+
const folders = await x.getAllBookmarkFolders();
|
|
141
|
+
|
|
142
|
+
// Get tweets from a folder
|
|
143
|
+
const folderTweets = await x.getAllBookmarkFolderTweets('folder-id');
|
|
144
|
+
|
|
145
|
+
// Get authenticated user profile
|
|
146
|
+
const me = await x.getMe();
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### BookmarkStore (Low-Level Cache)
|
|
150
|
+
|
|
151
|
+
Direct SQLite access for advanced queries.
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import { createStoreFromEnv } from '@hidden-leaf/x-skill';
|
|
155
|
+
const store = createStoreFromEnv();
|
|
156
|
+
|
|
157
|
+
const folders = store.getFolders();
|
|
158
|
+
const tweets = store.getTweetsByFolder('folder-id');
|
|
159
|
+
const stats = store.getStats();
|
|
160
|
+
store.close();
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Build Brief Prompt (Standalone)
|
|
164
|
+
|
|
165
|
+
Build the synthesis prompt without calling the API — useful for custom pipelines.
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
import { buildBriefPrompt } from '@hidden-leaf/x-skill';
|
|
169
|
+
|
|
170
|
+
const prompt = buildBriefPrompt(enrichedBookmarks, {
|
|
171
|
+
topic: 'AI Medicine',
|
|
172
|
+
hlnContext: ['Applied AI Studio'],
|
|
173
|
+
});
|
|
174
|
+
// Send this prompt to Claude API yourself
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Usage Patterns
|
|
178
|
+
|
|
179
|
+
### "Sync my bookmarks then list folders"
|
|
180
|
+
```typescript
|
|
181
|
+
import { createBookmarksSkillFromEnv } from '@hidden-leaf/x-skill';
|
|
182
|
+
const skill = createBookmarksSkillFromEnv();
|
|
183
|
+
|
|
184
|
+
// First sync (hits API, costs money)
|
|
185
|
+
await skill.syncAll();
|
|
186
|
+
|
|
187
|
+
// Then list from cache (free)
|
|
188
|
+
const { folders } = skill.listFolders();
|
|
189
|
+
for (const f of folders) {
|
|
190
|
+
console.log(`${f.name} (${f.tweet_count ?? '?'} posts)`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
skill.close();
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### "Give me a research brief on my AI Medicine bookmarks"
|
|
197
|
+
```typescript
|
|
198
|
+
import { createBookmarksSkillFromEnv } from '@hidden-leaf/x-skill';
|
|
199
|
+
const skill = createBookmarksSkillFromEnv();
|
|
200
|
+
const { brief } = await skill.brief({
|
|
201
|
+
folderName: 'AI MEDICINE',
|
|
202
|
+
hlnContext: ['Applied AI Studio', 'Orvex'],
|
|
203
|
+
});
|
|
204
|
+
console.log(brief.content);
|
|
205
|
+
skill.close();
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### "What's trending in my Quantum Computing saves?"
|
|
209
|
+
```typescript
|
|
210
|
+
import { createBookmarksSkillFromEnv } from '@hidden-leaf/x-skill';
|
|
211
|
+
const skill = createBookmarksSkillFromEnv();
|
|
212
|
+
const result = skill.fetchByFolderName('Quantum Computing');
|
|
213
|
+
console.log(`${result.totalTweets} posts from ${result.uniqueAuthors} authors`);
|
|
214
|
+
for (const b of result.bookmarks.slice(0, 10)) {
|
|
215
|
+
console.log(b.formatted);
|
|
216
|
+
}
|
|
217
|
+
skill.close();
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### "Create a Jira ticket from bookmark research" (with atlassian-skill)
|
|
221
|
+
```typescript
|
|
222
|
+
import { createBookmarksSkillFromEnv } from '@hidden-leaf/x-skill';
|
|
223
|
+
import { createJiraClientFromEnv, adf, text } from '@hidden-leaf/atlassian-skill';
|
|
224
|
+
|
|
225
|
+
const skill = createBookmarksSkillFromEnv();
|
|
226
|
+
const jira = createJiraClientFromEnv();
|
|
227
|
+
|
|
228
|
+
const { brief } = await skill.brief({ folderName: 'CHIPS' });
|
|
229
|
+
|
|
230
|
+
await jira.createIssue({
|
|
231
|
+
project: 'HLN',
|
|
232
|
+
issuetype: 'Task',
|
|
233
|
+
summary: `Research Brief: ${brief.topic} (${brief.tweetCount} posts)`,
|
|
234
|
+
description: adf().paragraph(text().text(brief.content)).build(),
|
|
235
|
+
labels: ['research', 'x-skill', 'auto-generated'],
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
skill.close();
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Error Handling
|
|
242
|
+
|
|
243
|
+
| Error | Cause | Recovery |
|
|
244
|
+
|-------|-------|----------|
|
|
245
|
+
| `XAuthenticationError` | Invalid/expired Bearer token | Regenerate token at developer.x.com |
|
|
246
|
+
| `XRateLimitError` | Rate limit hit | Wait `retryAfter` seconds, then retry |
|
|
247
|
+
| `XNotFoundError` | Endpoint or resource not found | Check user ID and folder IDs |
|
|
248
|
+
| `XApiRequestError` (403) | Missing OAuth scope or access level | Ensure `bookmark.read` scope |
|
|
249
|
+
| `"not found in cache"` | Cache empty for folder | Run `syncAll()` or `syncFolder()` first |
|
|
250
|
+
|
|
251
|
+
## Cost Notes
|
|
252
|
+
|
|
253
|
+
- X API uses pay-per-use pricing (Feb 2026+)
|
|
254
|
+
- **Sync is the only paid operation** — all reads hit the local SQLite cache for free
|
|
255
|
+
- Same post requested within 24h UTC window = single charge (deduplication)
|
|
256
|
+
- Estimated cost: ~$5-15/month for periodic syncs
|
|
257
|
+
- Recommend syncing once daily or on-demand, not continuously
|
|
258
|
+
|
|
259
|
+
## Downstream Consumers
|
|
260
|
+
|
|
261
|
+
Brief output is structured for ingestion by:
|
|
262
|
+
- **Jira tickets** via `@hidden-leaf/atlassian-skill`
|
|
263
|
+
- **ARIA v4** planning context and ambient research
|
|
264
|
+
- **Scroll** training data corpus
|
|
265
|
+
- **Applied AI Studio** client research deliverables
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite-backed local cache for X bookmark data.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: sync pulls from X API → upserts into local DB.
|
|
5
|
+
* All reads (list, fetch, brief) hit the cache, not the API.
|
|
6
|
+
* You only pay when you sync.
|
|
7
|
+
*/
|
|
8
|
+
import type { Tweet, User, BookmarkFolder } from '../clients/types.js';
|
|
9
|
+
export declare class BookmarkStore {
|
|
10
|
+
private readonly db;
|
|
11
|
+
constructor(dbPath?: string);
|
|
12
|
+
private migrate;
|
|
13
|
+
upsertFolder(folder: BookmarkFolder): void;
|
|
14
|
+
upsertUser(user: User): void;
|
|
15
|
+
upsertTweet(tweet: Tweet): void;
|
|
16
|
+
linkTweetToFolder(folderId: string, tweetId: string, position: number): void;
|
|
17
|
+
/**
|
|
18
|
+
* Upsert a full folder sync result in a single transaction.
|
|
19
|
+
*/
|
|
20
|
+
syncFolderData(folder: BookmarkFolder, tweets: Tweet[], users: Map<string, User>): void;
|
|
21
|
+
getFolders(): BookmarkFolder[];
|
|
22
|
+
getFolderByName(name: string): BookmarkFolder | undefined;
|
|
23
|
+
getTweetsByFolder(folderId: string): {
|
|
24
|
+
tweet: Tweet;
|
|
25
|
+
author: User | undefined;
|
|
26
|
+
}[];
|
|
27
|
+
getAllTweets(): {
|
|
28
|
+
tweet: Tweet;
|
|
29
|
+
author: User | undefined;
|
|
30
|
+
}[];
|
|
31
|
+
getStats(): {
|
|
32
|
+
folders: number;
|
|
33
|
+
tweets: number;
|
|
34
|
+
users: number;
|
|
35
|
+
lastSync: string | null;
|
|
36
|
+
};
|
|
37
|
+
logSyncStart(scope: string, folderId?: string): number;
|
|
38
|
+
logSyncComplete(logId: number, tweetCount: number): void;
|
|
39
|
+
logSyncError(logId: number, _error: string): void;
|
|
40
|
+
private rowToTweet;
|
|
41
|
+
private rowToUser;
|
|
42
|
+
close(): void;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Create a BookmarkStore with default or env-configured path.
|
|
46
|
+
*/
|
|
47
|
+
export declare function createStoreFromEnv(): BookmarkStore;
|
|
48
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/cache/store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAKvE,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAoB;gBAE3B,MAAM,CAAC,EAAE,MAAM;IAmB3B,OAAO,CAAC,OAAO;IA6Ef,YAAY,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI;IAsB1C,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IAkC5B,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAyC/B,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAgB5E;;OAEG;IACH,cAAc,CACZ,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,KAAK,EAAE,EACf,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,GACvB,IAAI;IAqBP,UAAU,IAAI,cAAc,EAAE;IAc9B,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAgBzD,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,KAAK,CAAC;QAAC,MAAM,EAAE,IAAI,GAAG,SAAS,CAAA;KAAE,EAAE;IAoBjF,YAAY,IAAI;QAAE,KAAK,EAAE,KAAK,CAAC;QAAC,MAAM,EAAE,IAAI,GAAG,SAAS,CAAA;KAAE,EAAE;IAkB5D,QAAQ,IAAI;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;IAevF,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM;IAStD,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAOxD,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAWjD,OAAO,CAAC,UAAU;IAkClB,OAAO,CAAC,SAAS;IAsBjB,KAAK,IAAI,IAAI;CAGd;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,aAAa,CAElD"}
|