@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.
Files changed (53) hide show
  1. package/.env.example +50 -0
  2. package/CLAUDE.snippet.md +34 -0
  3. package/README.md +220 -0
  4. package/SKILL.md +265 -0
  5. package/dist/cache/store.d.ts +48 -0
  6. package/dist/cache/store.d.ts.map +1 -0
  7. package/dist/cache/store.js +381 -0
  8. package/dist/cache/store.js.map +1 -0
  9. package/dist/clients/types.d.ts +217 -0
  10. package/dist/clients/types.d.ts.map +1 -0
  11. package/dist/clients/types.js +77 -0
  12. package/dist/clients/types.js.map +1 -0
  13. package/dist/clients/x-client.d.ts +111 -0
  14. package/dist/clients/x-client.d.ts.map +1 -0
  15. package/dist/clients/x-client.js +421 -0
  16. package/dist/clients/x-client.js.map +1 -0
  17. package/dist/index.d.ts +20 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +52 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/skills/bookmarks/index.d.ts +79 -0
  22. package/dist/skills/bookmarks/index.d.ts.map +1 -0
  23. package/dist/skills/bookmarks/index.js +288 -0
  24. package/dist/skills/bookmarks/index.js.map +1 -0
  25. package/dist/skills/bookmarks/synthesize.d.ts +40 -0
  26. package/dist/skills/bookmarks/synthesize.d.ts.map +1 -0
  27. package/dist/skills/bookmarks/synthesize.js +164 -0
  28. package/dist/skills/bookmarks/synthesize.js.map +1 -0
  29. package/dist/skills/bookmarks/types.d.ts +71 -0
  30. package/dist/skills/bookmarks/types.d.ts.map +1 -0
  31. package/dist/skills/bookmarks/types.js +6 -0
  32. package/dist/skills/bookmarks/types.js.map +1 -0
  33. package/dist/utils/logger.d.ts +10 -0
  34. package/dist/utils/logger.d.ts.map +1 -0
  35. package/dist/utils/logger.js +43 -0
  36. package/dist/utils/logger.js.map +1 -0
  37. package/package.json +68 -0
  38. package/scripts/cache-report.ts +25 -0
  39. package/scripts/create-atlsk-ticket.ts +68 -0
  40. package/scripts/create-deep-dive-ticket.ts +66 -0
  41. package/scripts/create-jira-project.ts +31 -0
  42. package/scripts/create-launch-and-roadmap.ts +200 -0
  43. package/scripts/create-next-session.ts +71 -0
  44. package/scripts/create-roadmap.ts +150 -0
  45. package/scripts/debug-api.ts +45 -0
  46. package/scripts/debug-folder.ts +37 -0
  47. package/scripts/jira-close-v1-tickets.ts +83 -0
  48. package/scripts/jira-v1-close-and-post-v1.ts +272 -0
  49. package/scripts/oauth-flow.ts +216 -0
  50. package/scripts/postinstall.js +112 -0
  51. package/scripts/sync-test.ts +36 -0
  52. package/scripts/test-refresh-forced.ts +29 -0
  53. package/scripts/test-refresh.ts +25 -0
package/dist/index.js ADDED
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ /**
3
+ * @hidden-leaf/x-skill
4
+ *
5
+ * X (Twitter) bookmark intelligence skill for Claude Code.
6
+ * Turns curated X bookmarks into structured research briefs.
7
+ *
8
+ * v1: Bookmark intelligence (list, fetch, brief)
9
+ * v2: Search, threads, profile (planned)
10
+ * v3: Publish, schedule, reply (planned)
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.VERSION = exports.createLogger = exports.createStoreFromEnv = exports.BookmarkStore = exports.generateBrief = exports.buildBriefPrompt = exports.createBookmarksSkillFromEnv = exports.BookmarksSkill = exports.DEFAULT_EXPANSIONS = exports.DEFAULT_USER_FIELDS = exports.DEFAULT_TWEET_FIELDS = exports.XNotFoundError = exports.XRateLimitError = exports.XAuthenticationError = exports.XApiRequestError = exports.createXClientFromEnv = exports.XClient = void 0;
14
+ // ============================================================================
15
+ // Client
16
+ // ============================================================================
17
+ var x_client_js_1 = require("./clients/x-client.js");
18
+ Object.defineProperty(exports, "XClient", { enumerable: true, get: function () { return x_client_js_1.XClient; } });
19
+ Object.defineProperty(exports, "createXClientFromEnv", { enumerable: true, get: function () { return x_client_js_1.createXClientFromEnv; } });
20
+ var types_js_1 = require("./clients/types.js");
21
+ Object.defineProperty(exports, "XApiRequestError", { enumerable: true, get: function () { return types_js_1.XApiRequestError; } });
22
+ Object.defineProperty(exports, "XAuthenticationError", { enumerable: true, get: function () { return types_js_1.XAuthenticationError; } });
23
+ Object.defineProperty(exports, "XRateLimitError", { enumerable: true, get: function () { return types_js_1.XRateLimitError; } });
24
+ Object.defineProperty(exports, "XNotFoundError", { enumerable: true, get: function () { return types_js_1.XNotFoundError; } });
25
+ Object.defineProperty(exports, "DEFAULT_TWEET_FIELDS", { enumerable: true, get: function () { return types_js_1.DEFAULT_TWEET_FIELDS; } });
26
+ Object.defineProperty(exports, "DEFAULT_USER_FIELDS", { enumerable: true, get: function () { return types_js_1.DEFAULT_USER_FIELDS; } });
27
+ Object.defineProperty(exports, "DEFAULT_EXPANSIONS", { enumerable: true, get: function () { return types_js_1.DEFAULT_EXPANSIONS; } });
28
+ // ============================================================================
29
+ // Bookmarks Skill
30
+ // ============================================================================
31
+ var index_js_1 = require("./skills/bookmarks/index.js");
32
+ Object.defineProperty(exports, "BookmarksSkill", { enumerable: true, get: function () { return index_js_1.BookmarksSkill; } });
33
+ Object.defineProperty(exports, "createBookmarksSkillFromEnv", { enumerable: true, get: function () { return index_js_1.createBookmarksSkillFromEnv; } });
34
+ var synthesize_js_1 = require("./skills/bookmarks/synthesize.js");
35
+ Object.defineProperty(exports, "buildBriefPrompt", { enumerable: true, get: function () { return synthesize_js_1.buildBriefPrompt; } });
36
+ Object.defineProperty(exports, "generateBrief", { enumerable: true, get: function () { return synthesize_js_1.generateBrief; } });
37
+ // ============================================================================
38
+ // Cache
39
+ // ============================================================================
40
+ var store_js_1 = require("./cache/store.js");
41
+ Object.defineProperty(exports, "BookmarkStore", { enumerable: true, get: function () { return store_js_1.BookmarkStore; } });
42
+ Object.defineProperty(exports, "createStoreFromEnv", { enumerable: true, get: function () { return store_js_1.createStoreFromEnv; } });
43
+ // ============================================================================
44
+ // Utilities
45
+ // ============================================================================
46
+ var logger_js_1 = require("./utils/logger.js");
47
+ Object.defineProperty(exports, "createLogger", { enumerable: true, get: function () { return logger_js_1.createLogger; } });
48
+ // ============================================================================
49
+ // Version
50
+ // ============================================================================
51
+ exports.VERSION = '1.0.0';
52
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;;AAEH,+EAA+E;AAC/E,SAAS;AACT,+EAA+E;AAE/E,qDAAsE;AAA7D,sGAAA,OAAO,OAAA;AAAE,mHAAA,oBAAoB,OAAA;AAiCtC,+CAQ4B;AAP1B,4GAAA,gBAAgB,OAAA;AAChB,gHAAA,oBAAoB,OAAA;AACpB,2GAAA,eAAe,OAAA;AACf,0GAAA,cAAc,OAAA;AACd,gHAAA,oBAAoB,OAAA;AACpB,+GAAA,mBAAmB,OAAA;AACnB,8GAAA,kBAAkB,OAAA;AAGpB,+EAA+E;AAC/E,kBAAkB;AAClB,+EAA+E;AAE/E,wDAA0F;AAAjF,0GAAA,cAAc,OAAA;AAAE,uHAAA,2BAA2B,OAAA;AACpD,kEAAmF;AAA1E,iHAAA,gBAAgB,OAAA;AAAE,8GAAA,aAAa,OAAA;AAExC,+EAA+E;AAC/E,QAAQ;AACR,+EAA+E;AAE/E,6CAAqE;AAA5D,yGAAA,aAAa,OAAA;AAAE,8GAAA,kBAAkB,OAAA;AAiB1C,+EAA+E;AAC/E,YAAY;AACZ,+EAA+E;AAE/E,+CAAiD;AAAxC,yGAAA,YAAY,OAAA;AAErB,+EAA+E;AAC/E,UAAU;AACV,+EAA+E;AAElE,QAAA,OAAO,GAAG,OAAO,CAAC"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Bookmarks skill — sync, list, fetch, brief.
3
+ *
4
+ * Architecture: sync pulls from X API → upserts into SQLite cache.
5
+ * All reads (list, fetch, brief) hit the cache, not the API.
6
+ * You only pay when you sync.
7
+ *
8
+ * v1 commands:
9
+ * x bookmarks sync → pull latest from X API into cache
10
+ * x bookmarks sync --folder "Robotics" → sync a single folder
11
+ * x bookmarks list → list all bookmark folders (from cache)
12
+ * x bookmarks fetch --folder "Robotics" → pull cached posts from a folder
13
+ * x bookmarks brief --folder "Robotics" → synthesize a research brief (from cache)
14
+ */
15
+ import { XClient } from '../../clients/x-client.js';
16
+ import { BookmarkStore } from '../../cache/store.js';
17
+ import type { BookmarkListOutput, BookmarkFetchOutput, BriefOptions, BookmarkBriefOutput, SyncResult } from './types.js';
18
+ export declare class BookmarksSkill {
19
+ private readonly client;
20
+ private readonly store;
21
+ constructor(client: XClient, store?: BookmarkStore);
22
+ /**
23
+ * Sync all bookmark folders and their tweets from X API into local cache.
24
+ * This is the only command that hits the X API and costs money.
25
+ *
26
+ * Strategy (minimizes API calls):
27
+ * 1. Fetch folder list + tweet IDs per folder (lightweight, IDs only)
28
+ * 2. Fetch ALL bookmarks with full data from main endpoint (paginated)
29
+ * 3. Hydrate any missing tweets via GET /2/tweets lookup (100 per call)
30
+ * 4. Cross-reference IDs to assign tweets to folders in the cache
31
+ */
32
+ syncAll(): Promise<SyncResult>;
33
+ /**
34
+ * Sync a single folder by name.
35
+ * Fetches folder IDs, then hydrates via tweet lookup.
36
+ */
37
+ syncFolder(folderName: string): Promise<SyncResult>;
38
+ /**
39
+ * List all bookmark folders from local cache.
40
+ * Run `sync` first if cache is empty.
41
+ */
42
+ listFolders(): BookmarkListOutput;
43
+ /**
44
+ * Fetch cached bookmarked tweets from a folder by name.
45
+ */
46
+ fetchByFolderName(folderName: string): BookmarkFetchOutput;
47
+ /**
48
+ * Fetch cached bookmarked tweets from a folder by ID.
49
+ */
50
+ fetchByFolderId(folderId: string, folderName?: string): BookmarkFetchOutput;
51
+ /**
52
+ * Fetch ALL cached bookmarks across all folders.
53
+ */
54
+ fetchAll(): BookmarkFetchOutput;
55
+ /**
56
+ * Generate a research brief from cached bookmarks.
57
+ */
58
+ brief(options?: BriefOptions): Promise<BookmarkBriefOutput>;
59
+ /**
60
+ * Get cache statistics.
61
+ */
62
+ stats(): {
63
+ folders: number;
64
+ tweets: number;
65
+ users: number;
66
+ lastSync: string | null;
67
+ };
68
+ private toEnrichedBookmark;
69
+ /**
70
+ * Close the underlying database connection.
71
+ */
72
+ close(): void;
73
+ }
74
+ /**
75
+ * Create a BookmarksSkill from environment variables.
76
+ * Reads X_BEARER_TOKEN, X_USER_ID, and optionally X_CACHE_DB_PATH from .env.
77
+ */
78
+ export declare function createBookmarksSkillFromEnv(): BookmarksSkill;
79
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/skills/bookmarks/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,OAAO,EAAwB,MAAM,2BAA2B,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAsB,MAAM,sBAAsB,CAAC;AAEzE,OAAO,KAAK,EAEV,kBAAkB,EAClB,mBAAmB,EACnB,YAAY,EACZ,mBAAmB,EACnB,UAAU,EACX,MAAM,YAAY,CAAC;AAGpB,qBAAa,cAAc;IAIvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAHzB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgB;gBAGnB,MAAM,EAAE,OAAO,EAChC,KAAK,CAAC,EAAE,aAAa;IASvB;;;;;;;;;OASG;IACG,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC;IA+EpC;;;OAGG;IACG,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAgEzD;;;OAGG;IACH,WAAW,IAAI,kBAAkB;IAajC;;OAEG;IACH,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,mBAAmB;IAa1D;;OAEG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,mBAAmB;IAgB3E;;OAEG;IACH,QAAQ,IAAI,mBAAmB;IAoB/B;;OAEG;IACG,KAAK,CAAC,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAoCrE;;OAEG;IACH,KAAK,IAAI;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;IAQpF,OAAO,CAAC,kBAAkB;IAY1B;;OAEG;IACH,KAAK,IAAI,IAAI;CAGd;AAMD;;;GAGG;AACH,wBAAgB,2BAA2B,IAAI,cAAc,CAI5D"}
@@ -0,0 +1,288 @@
1
+ "use strict";
2
+ /**
3
+ * Bookmarks skill — sync, list, fetch, brief.
4
+ *
5
+ * Architecture: sync pulls from X API → upserts into SQLite cache.
6
+ * All reads (list, fetch, brief) hit the cache, not the API.
7
+ * You only pay when you sync.
8
+ *
9
+ * v1 commands:
10
+ * x bookmarks sync → pull latest from X API into cache
11
+ * x bookmarks sync --folder "Robotics" → sync a single folder
12
+ * x bookmarks list → list all bookmark folders (from cache)
13
+ * x bookmarks fetch --folder "Robotics" → pull cached posts from a folder
14
+ * x bookmarks brief --folder "Robotics" → synthesize a research brief (from cache)
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.BookmarksSkill = void 0;
18
+ exports.createBookmarksSkillFromEnv = createBookmarksSkillFromEnv;
19
+ const x_client_js_1 = require("../../clients/x-client.js");
20
+ const store_js_1 = require("../../cache/store.js");
21
+ const synthesize_js_1 = require("./synthesize.js");
22
+ class BookmarksSkill {
23
+ client;
24
+ store;
25
+ constructor(client, store) {
26
+ this.client = client;
27
+ this.store = store ?? (0, store_js_1.createStoreFromEnv)();
28
+ }
29
+ // ==========================================================================
30
+ // x bookmarks sync
31
+ // ==========================================================================
32
+ /**
33
+ * Sync all bookmark folders and their tweets from X API into local cache.
34
+ * This is the only command that hits the X API and costs money.
35
+ *
36
+ * Strategy (minimizes API calls):
37
+ * 1. Fetch folder list + tweet IDs per folder (lightweight, IDs only)
38
+ * 2. Fetch ALL bookmarks with full data from main endpoint (paginated)
39
+ * 3. Hydrate any missing tweets via GET /2/tweets lookup (100 per call)
40
+ * 4. Cross-reference IDs to assign tweets to folders in the cache
41
+ */
42
+ async syncAll() {
43
+ const logId = this.store.logSyncStart('all');
44
+ try {
45
+ const { tweets, users, folders, folderTweetIds } = await this.client.getBookmarksWithFolders();
46
+ // Build a tweet lookup map from main bookmarks endpoint
47
+ const tweetMap = new Map(tweets.map((t) => [t.id, t]));
48
+ // Collect all folder tweet IDs that are NOT in the main bookmarks response
49
+ const allFolderIds = new Set();
50
+ for (const ids of folderTweetIds.values()) {
51
+ for (const id of ids) {
52
+ allFolderIds.add(id);
53
+ }
54
+ }
55
+ const missingIds = [...allFolderIds].filter((id) => !tweetMap.has(id));
56
+ // Hydrate missing tweets via lookup endpoint
57
+ if (missingIds.length > 0) {
58
+ const { tweets: hydrated, users: hydratedUsers } = await this.client.getTweetsByIds(missingIds);
59
+ for (const tweet of hydrated) {
60
+ tweetMap.set(tweet.id, tweet);
61
+ }
62
+ for (const [id, user] of hydratedUsers) {
63
+ users.set(id, user);
64
+ }
65
+ }
66
+ // Upsert all users
67
+ for (const user of users.values()) {
68
+ this.store.upsertUser(user);
69
+ }
70
+ // Upsert all tweets
71
+ for (const tweet of tweetMap.values()) {
72
+ this.store.upsertTweet(tweet);
73
+ }
74
+ // Upsert folders and link tweets
75
+ const folderResults = [];
76
+ for (const folder of folders) {
77
+ const tweetIds = folderTweetIds.get(folder.id) ?? [];
78
+ this.store.upsertFolder({ ...folder, tweet_count: tweetIds.length });
79
+ // Link tweets to folder (only those we have data for)
80
+ let linkedCount = 0;
81
+ for (let i = 0; i < tweetIds.length; i++) {
82
+ if (tweetMap.has(tweetIds[i])) {
83
+ this.store.linkTweetToFolder(folder.id, tweetIds[i], i);
84
+ linkedCount++;
85
+ }
86
+ }
87
+ folderResults.push({
88
+ name: folder.name,
89
+ id: folder.id,
90
+ tweetCount: linkedCount,
91
+ });
92
+ }
93
+ const totalTweets = tweetMap.size;
94
+ this.store.logSyncComplete(logId, totalTweets);
95
+ return {
96
+ syncedAt: new Date().toISOString(),
97
+ totalFolders: folders.length,
98
+ totalTweets,
99
+ folders: folderResults,
100
+ };
101
+ }
102
+ catch (error) {
103
+ this.store.logSyncError(logId, String(error));
104
+ throw error;
105
+ }
106
+ }
107
+ /**
108
+ * Sync a single folder by name.
109
+ * Fetches folder IDs, then hydrates via tweet lookup.
110
+ */
111
+ async syncFolder(folderName) {
112
+ const logId = this.store.logSyncStart('folder');
113
+ try {
114
+ const folders = await this.client.getAllBookmarkFolders();
115
+ const normalizedName = folderName.toLowerCase().trim();
116
+ const folder = folders.find((f) => f.name.toLowerCase().trim() === normalizedName);
117
+ if (!folder) {
118
+ const available = folders.map((f) => f.name).join(', ');
119
+ throw new Error(`Bookmark folder "${folderName}" not found. Available: ${available}`);
120
+ }
121
+ // Get IDs for this folder
122
+ const tweetIds = await this.client.getBookmarkFolderTweetIds(folder.id);
123
+ // Hydrate all via tweet lookup (full data, 100 per call)
124
+ const { tweets, users } = await this.client.getTweetsByIds(tweetIds);
125
+ const tweetMap = new Map(tweets.map((t) => [t.id, t]));
126
+ // Upsert users + tweets
127
+ for (const user of users.values()) {
128
+ this.store.upsertUser(user);
129
+ }
130
+ for (const tweet of tweets) {
131
+ this.store.upsertTweet(tweet);
132
+ }
133
+ // Upsert all folders (keeps list current) + link target folder tweets
134
+ for (const f of folders) {
135
+ this.store.upsertFolder(f);
136
+ }
137
+ this.store.upsertFolder({ ...folder, tweet_count: tweetIds.length });
138
+ let linkedCount = 0;
139
+ for (let i = 0; i < tweetIds.length; i++) {
140
+ if (tweetMap.has(tweetIds[i])) {
141
+ this.store.linkTweetToFolder(folder.id, tweetIds[i], i);
142
+ linkedCount++;
143
+ }
144
+ }
145
+ this.store.logSyncComplete(logId, linkedCount);
146
+ return {
147
+ syncedAt: new Date().toISOString(),
148
+ totalFolders: 1,
149
+ totalTweets: linkedCount,
150
+ folders: [{ name: folder.name, id: folder.id, tweetCount: linkedCount }],
151
+ };
152
+ }
153
+ catch (error) {
154
+ this.store.logSyncError(logId, String(error));
155
+ throw error;
156
+ }
157
+ }
158
+ // ==========================================================================
159
+ // x bookmarks list (reads from cache)
160
+ // ==========================================================================
161
+ /**
162
+ * List all bookmark folders from local cache.
163
+ * Run `sync` first if cache is empty.
164
+ */
165
+ listFolders() {
166
+ const folders = this.store.getFolders();
167
+ return {
168
+ folders,
169
+ totalFolders: folders.length,
170
+ };
171
+ }
172
+ // ==========================================================================
173
+ // x bookmarks fetch (reads from cache)
174
+ // ==========================================================================
175
+ /**
176
+ * Fetch cached bookmarked tweets from a folder by name.
177
+ */
178
+ fetchByFolderName(folderName) {
179
+ const folder = this.store.getFolderByName(folderName);
180
+ if (!folder) {
181
+ const available = this.store.getFolders().map((f) => f.name).join(', ');
182
+ throw new Error(`Folder "${folderName}" not found in cache. Available: ${available || 'none (run sync first)'}`);
183
+ }
184
+ return this.fetchByFolderId(folder.id, folder.name);
185
+ }
186
+ /**
187
+ * Fetch cached bookmarked tweets from a folder by ID.
188
+ */
189
+ fetchByFolderId(folderId, folderName) {
190
+ const rows = this.store.getTweetsByFolder(folderId);
191
+ const bookmarks = rows.map((r) => this.toEnrichedBookmark(r.tweet, r.author));
192
+ const uniqueAuthors = new Set(bookmarks.map((b) => b.author?.username).filter(Boolean));
193
+ return {
194
+ folder: folderName ?? folderId,
195
+ bookmarks,
196
+ totalTweets: bookmarks.length,
197
+ uniqueAuthors: uniqueAuthors.size,
198
+ };
199
+ }
200
+ /**
201
+ * Fetch ALL cached bookmarks across all folders.
202
+ */
203
+ fetchAll() {
204
+ const rows = this.store.getAllTweets();
205
+ const bookmarks = rows.map((r) => this.toEnrichedBookmark(r.tweet, r.author));
206
+ const uniqueAuthors = new Set(bookmarks.map((b) => b.author?.username).filter(Boolean));
207
+ return {
208
+ folder: 'All Bookmarks',
209
+ bookmarks,
210
+ totalTweets: bookmarks.length,
211
+ uniqueAuthors: uniqueAuthors.size,
212
+ };
213
+ }
214
+ // ==========================================================================
215
+ // x bookmarks brief (reads from cache)
216
+ // ==========================================================================
217
+ /**
218
+ * Generate a research brief from cached bookmarks.
219
+ */
220
+ async brief(options = {}) {
221
+ let fetchResult;
222
+ if (options.folderId) {
223
+ fetchResult = this.fetchByFolderId(options.folderId, options.folderName);
224
+ }
225
+ else if (options.folderName) {
226
+ fetchResult = this.fetchByFolderName(options.folderName);
227
+ }
228
+ else {
229
+ fetchResult = this.fetchAll();
230
+ }
231
+ if (fetchResult.totalTweets === 0) {
232
+ throw new Error(`No bookmarks found in cache for "${options.folderName ?? 'all'}". Run sync first.`);
233
+ }
234
+ const maxTweets = options.maxTweets ?? 50;
235
+ const sourceTweets = fetchResult.bookmarks.slice(0, maxTweets);
236
+ const brief = await (0, synthesize_js_1.generateBrief)(sourceTweets, {
237
+ topic: fetchResult.folder,
238
+ customPrompt: options.customPrompt,
239
+ hlnContext: options.hlnContext,
240
+ });
241
+ return {
242
+ brief,
243
+ sourceBookmarks: sourceTweets,
244
+ };
245
+ }
246
+ // ==========================================================================
247
+ // x bookmarks stats
248
+ // ==========================================================================
249
+ /**
250
+ * Get cache statistics.
251
+ */
252
+ stats() {
253
+ return this.store.getStats();
254
+ }
255
+ // ==========================================================================
256
+ // Helpers
257
+ // ==========================================================================
258
+ toEnrichedBookmark(tweet, author) {
259
+ const metrics = tweet.public_metrics;
260
+ const handle = author ? `@${author.username}` : 'unknown';
261
+ const stats = metrics
262
+ ? ` (${metrics.like_count} likes, ${metrics.retweet_count} RTs)`
263
+ : '';
264
+ const text = tweet.note_tweet?.text ?? tweet.text;
265
+ const formatted = `${handle}: ${text}${stats}`;
266
+ return { tweet, author, formatted };
267
+ }
268
+ /**
269
+ * Close the underlying database connection.
270
+ */
271
+ close() {
272
+ this.store.close();
273
+ }
274
+ }
275
+ exports.BookmarksSkill = BookmarksSkill;
276
+ // ============================================================================
277
+ // Factory
278
+ // ============================================================================
279
+ /**
280
+ * Create a BookmarksSkill from environment variables.
281
+ * Reads X_BEARER_TOKEN, X_USER_ID, and optionally X_CACHE_DB_PATH from .env.
282
+ */
283
+ function createBookmarksSkillFromEnv() {
284
+ const client = (0, x_client_js_1.createXClientFromEnv)();
285
+ const store = (0, store_js_1.createStoreFromEnv)();
286
+ return new BookmarksSkill(client, store);
287
+ }
288
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/skills/bookmarks/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;;AAmVH,kEAIC;AArVD,2DAA0E;AAC1E,mDAAyE;AAUzE,mDAAgD;AAEhD,MAAa,cAAc;IAIN;IAHF,KAAK,CAAgB;IAEtC,YACmB,MAAe,EAChC,KAAqB;QADJ,WAAM,GAAN,MAAM,CAAS;QAGhC,IAAI,CAAC,KAAK,GAAG,KAAK,IAAI,IAAA,6BAAkB,GAAE,CAAC;IAC7C,CAAC;IAED,6EAA6E;IAC7E,mBAAmB;IACnB,6EAA6E;IAE7E;;;;;;;;;OASG;IACH,KAAK,CAAC,OAAO;QACX,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;QAE7C,IAAI,CAAC;YACH,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,GAC9C,MAAM,IAAI,CAAC,MAAM,CAAC,uBAAuB,EAAE,CAAC;YAE9C,wDAAwD;YACxD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YAEvD,2EAA2E;YAC3E,MAAM,YAAY,GAAG,IAAI,GAAG,EAAU,CAAC;YACvC,KAAK,MAAM,GAAG,IAAI,cAAc,CAAC,MAAM,EAAE,EAAE,CAAC;gBAC1C,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;oBACrB,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBACvB,CAAC;YACH,CAAC;YACD,MAAM,UAAU,GAAG,CAAC,GAAG,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;YAEvE,6CAA6C;YAC7C,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC1B,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,aAAa,EAAE,GAC9C,MAAM,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;gBAE/C,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;oBAC7B,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;gBAChC,CAAC;gBACD,KAAK,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,aAAa,EAAE,CAAC;oBACvC,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;gBACtB,CAAC;YACH,CAAC;YAED,mBAAmB;YACnB,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;gBAClC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;YAC9B,CAAC;YAED,oBAAoB;YACpB,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;gBACtC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YAChC,CAAC;YAED,iCAAiC;YACjC,MAAM,aAAa,GAA0B,EAAE,CAAC;YAChD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC7B,MAAM,QAAQ,GAAG,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;gBACrD,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;gBAErE,sDAAsD;gBACtD,IAAI,WAAW,GAAG,CAAC,CAAC;gBACpB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;oBACzC,IAAI,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;wBAC9B,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;wBACxD,WAAW,EAAE,CAAC;oBAChB,CAAC;gBACH,CAAC;gBAED,aAAa,CAAC,IAAI,CAAC;oBACjB,IAAI,EAAE,MAAM,CAAC,IAAI;oBACjB,EAAE,EAAE,MAAM,CAAC,EAAE;oBACb,UAAU,EAAE,WAAW;iBACxB,CAAC,CAAC;YACL,CAAC;YAED,MAAM,WAAW,GAAG,QAAQ,CAAC,IAAI,CAAC;YAClC,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;YAE/C,OAAO;gBACL,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBAClC,YAAY,EAAE,OAAO,CAAC,MAAM;gBAC5B,WAAW;gBACX,OAAO,EAAE,aAAa;aACvB,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YAC9C,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,UAAU,CAAC,UAAkB;QACjC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;QAEhD,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,qBAAqB,EAAE,CAAC;YAC1D,MAAM,cAAc,GAAG,UAAU,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;YACvD,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CACzB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,KAAK,cAAc,CACtD,CAAC;YAEF,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACxD,MAAM,IAAI,KAAK,CACb,oBAAoB,UAAU,2BAA2B,SAAS,EAAE,CACrE,CAAC;YACJ,CAAC;YAED,0BAA0B;YAC1B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,yBAAyB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAExE,yDAAyD;YACzD,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;YACrE,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YAEvD,wBAAwB;YACxB,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;gBAClC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;YAC9B,CAAC;YACD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC3B,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YAChC,CAAC;YAED,sEAAsE;YACtE,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;gBACxB,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAC7B,CAAC;YACD,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;YAErE,IAAI,WAAW,GAAG,CAAC,CAAC;YACpB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACzC,IAAI,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;oBAC9B,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;oBACxD,WAAW,EAAE,CAAC;gBAChB,CAAC;YACH,CAAC;YAED,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;YAE/C,OAAO;gBACL,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBAClC,YAAY,EAAE,CAAC;gBACf,WAAW,EAAE,WAAW;gBACxB,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC;aACzE,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YAC9C,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED,6EAA6E;IAC7E,sCAAsC;IACtC,6EAA6E;IAE7E;;;OAGG;IACH,WAAW;QACT,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;QAExC,OAAO;YACL,OAAO;YACP,YAAY,EAAE,OAAO,CAAC,MAAM;SAC7B,CAAC;IACJ,CAAC;IAED,6EAA6E;IAC7E,uCAAuC;IACvC,6EAA6E;IAE7E;;OAEG;IACH,iBAAiB,CAAC,UAAkB;QAClC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;QAEtD,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACxE,MAAM,IAAI,KAAK,CACb,WAAW,UAAU,oCAAoC,SAAS,IAAI,uBAAuB,EAAE,CAChG,CAAC;QACJ,CAAC;QAED,OAAO,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;IACtD,CAAC;IAED;;OAEG;IACH,eAAe,CAAC,QAAgB,EAAE,UAAmB;QACnD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QACpD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QAE9E,MAAM,aAAa,GAAG,IAAI,GAAG,CAC3B,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CACzD,CAAC;QAEF,OAAO;YACL,MAAM,EAAE,UAAU,IAAI,QAAQ;YAC9B,SAAS;YACT,WAAW,EAAE,SAAS,CAAC,MAAM;YAC7B,aAAa,EAAE,aAAa,CAAC,IAAI;SAClC,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,QAAQ;QACN,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE,CAAC;QACvC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QAE9E,MAAM,aAAa,GAAG,IAAI,GAAG,CAC3B,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CACzD,CAAC;QAEF,OAAO;YACL,MAAM,EAAE,eAAe;YACvB,SAAS;YACT,WAAW,EAAE,SAAS,CAAC,MAAM;YAC7B,aAAa,EAAE,aAAa,CAAC,IAAI;SAClC,CAAC;IACJ,CAAC;IAED,6EAA6E;IAC7E,uCAAuC;IACvC,6EAA6E;IAE7E;;OAEG;IACH,KAAK,CAAC,KAAK,CAAC,UAAwB,EAAE;QACpC,IAAI,WAAgC,CAAC;QAErC,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACrB,WAAW,GAAG,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;QAC3E,CAAC;aAAM,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;YAC9B,WAAW,GAAG,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAC3D,CAAC;aAAM,CAAC;YACN,WAAW,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QAChC,CAAC;QAED,IAAI,WAAW,CAAC,WAAW,KAAK,CAAC,EAAE,CAAC;YAClC,MAAM,IAAI,KAAK,CACb,oCAAoC,OAAO,CAAC,UAAU,IAAI,KAAK,oBAAoB,CACpF,CAAC;QACJ,CAAC;QAED,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC;QAC1C,MAAM,YAAY,GAAG,WAAW,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;QAE/D,MAAM,KAAK,GAAG,MAAM,IAAA,6BAAa,EAAC,YAAY,EAAE;YAC9C,KAAK,EAAE,WAAW,CAAC,MAAM;YACzB,YAAY,EAAE,OAAO,CAAC,YAAY;YAClC,UAAU,EAAE,OAAO,CAAC,UAAU;SAC/B,CAAC,CAAC;QAEH,OAAO;YACL,KAAK;YACL,eAAe,EAAE,YAAY;SAC9B,CAAC;IACJ,CAAC;IAED,6EAA6E;IAC7E,oBAAoB;IACpB,6EAA6E;IAE7E;;OAEG;IACH,KAAK;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;IAC/B,CAAC;IAED,6EAA6E;IAC7E,UAAU;IACV,6EAA6E;IAErE,kBAAkB,CAAC,KAAY,EAAE,MAAwB;QAC/D,MAAM,OAAO,GAAG,KAAK,CAAC,cAAc,CAAC;QACrC,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QAC1D,MAAM,KAAK,GAAG,OAAO;YACnB,CAAC,CAAC,KAAK,OAAO,CAAC,UAAU,WAAW,OAAO,CAAC,aAAa,OAAO;YAChE,CAAC,CAAC,EAAE,CAAC;QACP,MAAM,IAAI,GAAG,KAAK,CAAC,UAAU,EAAE,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC;QAClD,MAAM,SAAS,GAAG,GAAG,MAAM,KAAK,IAAI,GAAG,KAAK,EAAE,CAAC;QAE/C,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IACtC,CAAC;IAED;;OAEG;IACH,KAAK;QACH,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;CACF;AA1TD,wCA0TC;AAED,+EAA+E;AAC/E,UAAU;AACV,+EAA+E;AAE/E;;;GAGG;AACH,SAAgB,2BAA2B;IACzC,MAAM,MAAM,GAAG,IAAA,kCAAoB,GAAE,CAAC;IACtC,MAAM,KAAK,GAAG,IAAA,6BAAkB,GAAE,CAAC;IACnC,OAAO,IAAI,cAAc,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AAC3C,CAAC"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Brief synthesis — pipes bookmark posts through Claude and outputs
3
+ * structured markdown covering themes, notable voices, signal vs. noise,
4
+ * and HLN-specific relevance.
5
+ *
6
+ * This module is designed to be called by Claude Code itself. The output
7
+ * is a structured ResearchBrief object that can be piped downstream to:
8
+ * - Jira tickets (via atlassian-skill)
9
+ * - ARIA context / planning prompts
10
+ * - Scroll training data corpus
11
+ * - Applied AI Studio client research
12
+ */
13
+ import type { EnrichedBookmark, ResearchBrief } from './types.js';
14
+ interface SynthesizeOptions {
15
+ topic: string;
16
+ customPrompt?: string;
17
+ hlnContext?: string[];
18
+ }
19
+ /**
20
+ * Build the synthesis prompt from enriched bookmarks.
21
+ * Returns the full prompt string — caller is responsible for sending to Claude.
22
+ *
23
+ * Design decision: this skill does NOT call the Claude API directly.
24
+ * Instead, it builds a prompt + structured input that Claude Code can execute.
25
+ * This keeps the skill stateless and avoids requiring a separate API key.
26
+ */
27
+ export declare function buildBriefPrompt(bookmarks: EnrichedBookmark[], options: SynthesizeOptions): string;
28
+ /**
29
+ * Generate a research brief from enriched bookmarks.
30
+ *
31
+ * This function builds the prompt and parses the response into a structured
32
+ * ResearchBrief object. The actual Claude call happens inline — when this
33
+ * skill is invoked from Claude Code, the model is already available.
34
+ *
35
+ * For programmatic use outside Claude Code, the caller should use
36
+ * buildBriefPrompt() and send it to the Claude API themselves.
37
+ */
38
+ export declare function generateBrief(bookmarks: EnrichedBookmark[], options: SynthesizeOptions): Promise<ResearchBrief>;
39
+ export {};
40
+ //# sourceMappingURL=synthesize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"synthesize.d.ts","sourceRoot":"","sources":["../../../src/skills/bookmarks/synthesize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAElE,UAAU,iBAAiB;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,EAAE,iBAAiB,GACzB,MAAM,CA0ER;AAED;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CACjC,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,aAAa,CAAC,CAgBxB"}
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ /**
3
+ * Brief synthesis — pipes bookmark posts through Claude and outputs
4
+ * structured markdown covering themes, notable voices, signal vs. noise,
5
+ * and HLN-specific relevance.
6
+ *
7
+ * This module is designed to be called by Claude Code itself. The output
8
+ * is a structured ResearchBrief object that can be piped downstream to:
9
+ * - Jira tickets (via atlassian-skill)
10
+ * - ARIA context / planning prompts
11
+ * - Scroll training data corpus
12
+ * - Applied AI Studio client research
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.buildBriefPrompt = buildBriefPrompt;
16
+ exports.generateBrief = generateBrief;
17
+ /**
18
+ * Build the synthesis prompt from enriched bookmarks.
19
+ * Returns the full prompt string — caller is responsible for sending to Claude.
20
+ *
21
+ * Design decision: this skill does NOT call the Claude API directly.
22
+ * Instead, it builds a prompt + structured input that Claude Code can execute.
23
+ * This keeps the skill stateless and avoids requiring a separate API key.
24
+ */
25
+ function buildBriefPrompt(bookmarks, options) {
26
+ const { topic, customPrompt, hlnContext } = options;
27
+ const postsBlock = bookmarks
28
+ .map((b, i) => {
29
+ const handle = b.author ? `@${b.author.username}` : 'unknown';
30
+ const name = b.author?.name ?? 'Unknown';
31
+ const text = b.tweet.note_tweet?.text ?? b.tweet.text;
32
+ const date = b.tweet.created_at ?? 'unknown date';
33
+ const metrics = b.tweet.public_metrics;
34
+ const stats = metrics
35
+ ? `Likes: ${metrics.like_count} | RTs: ${metrics.retweet_count} | Quotes: ${metrics.quote_count}`
36
+ : 'no metrics';
37
+ const urls = b.tweet.entities?.urls
38
+ ?.map((u) => u.expanded_url)
39
+ .join(', ') ?? '';
40
+ return [
41
+ `--- Post ${i + 1} ---`,
42
+ `Author: ${name} (${handle})`,
43
+ `Date: ${date}`,
44
+ `Engagement: ${stats}`,
45
+ urls ? `Links: ${urls}` : '',
46
+ `Text: ${text}`,
47
+ ]
48
+ .filter(Boolean)
49
+ .join('\n');
50
+ })
51
+ .join('\n\n');
52
+ const hlnSection = hlnContext?.length
53
+ ? `\n## HLN Context\nRelate findings to these HLN ventures/initiatives: ${hlnContext.join(', ')}`
54
+ : '';
55
+ const customSection = customPrompt
56
+ ? `\n## Additional Instructions\n${customPrompt}`
57
+ : '';
58
+ return `You are a research analyst for Hidden Leaf Networks (HLN). Analyze the following ${bookmarks.length} bookmarked X posts from the "${topic}" folder and produce a structured research brief.
59
+
60
+ ## Output Format
61
+
62
+ Produce a markdown document with these sections:
63
+
64
+ ### Key Themes
65
+ - Identify 3-7 major themes or trends across the posts
66
+ - For each theme, cite 1-2 specific posts as evidence
67
+
68
+ ### Notable Voices
69
+ - List the most influential or insightful accounts in this set
70
+ - Note their area of expertise and why they're worth following
71
+
72
+ ### Signal vs. Noise
73
+ - **High signal:** Posts with genuine insight, data, or novel perspective
74
+ - **Low signal:** Posts that are hype, repetitive, or lack substance
75
+ - Assign a signal quality score (1-10) for the overall folder
76
+
77
+ ### Actionable Intelligence
78
+ - What should HLN act on based on these posts?
79
+ - Any emerging opportunities, risks, or inflection points?
80
+
81
+ ### HLN Relevance
82
+ - Which HLN ventures or initiatives does this research impact?
83
+ - Specific recommendations for how to use this intelligence
84
+ ${hlnSection}
85
+ ${customSection}
86
+
87
+ ## Bookmarked Posts
88
+
89
+ ${postsBlock}
90
+
91
+ ---
92
+ Produce the brief now. Be concise but thorough. Prioritize actionable intelligence over summary.`;
93
+ }
94
+ /**
95
+ * Generate a research brief from enriched bookmarks.
96
+ *
97
+ * This function builds the prompt and parses the response into a structured
98
+ * ResearchBrief object. The actual Claude call happens inline — when this
99
+ * skill is invoked from Claude Code, the model is already available.
100
+ *
101
+ * For programmatic use outside Claude Code, the caller should use
102
+ * buildBriefPrompt() and send it to the Claude API themselves.
103
+ */
104
+ async function generateBrief(bookmarks, options) {
105
+ const prompt = buildBriefPrompt(bookmarks, options);
106
+ // Extract structured metadata from bookmarks for the brief envelope
107
+ const notableVoices = extractNotableVoices(bookmarks);
108
+ const themes = extractThemes(bookmarks);
109
+ return {
110
+ topic: options.topic,
111
+ generatedAt: new Date().toISOString(),
112
+ tweetCount: bookmarks.length,
113
+ content: prompt, // The prompt itself — Claude Code will synthesize when invoked
114
+ notableVoices,
115
+ themes,
116
+ hlnRelevance: options.hlnContext ?? [],
117
+ };
118
+ }
119
+ /**
120
+ * Extract notable voices — accounts with highest engagement across bookmarks.
121
+ */
122
+ function extractNotableVoices(bookmarks) {
123
+ const authorEngagement = new Map();
124
+ for (const b of bookmarks) {
125
+ if (!b.author)
126
+ continue;
127
+ const handle = `@${b.author.username}`;
128
+ const engagement = (b.tweet.public_metrics?.like_count ?? 0) +
129
+ (b.tweet.public_metrics?.retweet_count ?? 0) * 2 +
130
+ (b.tweet.public_metrics?.quote_count ?? 0) * 3;
131
+ authorEngagement.set(handle, (authorEngagement.get(handle) ?? 0) + engagement);
132
+ }
133
+ return [...authorEngagement.entries()]
134
+ .sort((a, b) => b[1] - a[1])
135
+ .slice(0, 10)
136
+ .map(([handle]) => handle);
137
+ }
138
+ /**
139
+ * Extract preliminary themes from context annotations and hashtags.
140
+ */
141
+ function extractThemes(bookmarks) {
142
+ const themeCount = new Map();
143
+ for (const b of bookmarks) {
144
+ // Context annotations (X's ML-derived topics)
145
+ if (b.tweet.context_annotations) {
146
+ for (const ann of b.tweet.context_annotations) {
147
+ const name = ann.entity.name;
148
+ themeCount.set(name, (themeCount.get(name) ?? 0) + 1);
149
+ }
150
+ }
151
+ // Hashtags
152
+ if (b.tweet.entities?.hashtags) {
153
+ for (const ht of b.tweet.entities.hashtags) {
154
+ const tag = `#${ht.tag}`;
155
+ themeCount.set(tag, (themeCount.get(tag) ?? 0) + 1);
156
+ }
157
+ }
158
+ }
159
+ return [...themeCount.entries()]
160
+ .sort((a, b) => b[1] - a[1])
161
+ .slice(0, 15)
162
+ .map(([theme]) => theme);
163
+ }
164
+ //# sourceMappingURL=synthesize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"synthesize.js","sourceRoot":"","sources":["../../../src/skills/bookmarks/synthesize.ts"],"names":[],"mappings":"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"}