@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/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":"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"}
|
|
@@ -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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"}
|