@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
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* X API v2 types for @hidden-leaf/x-skill
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.XNotFoundError = exports.XRateLimitError = exports.XAuthenticationError = exports.XApiRequestError = exports.DEFAULT_EXPANSIONS = exports.DEFAULT_USER_FIELDS = exports.DEFAULT_TWEET_FIELDS = void 0;
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Request Params
|
|
9
|
+
// ============================================================================
|
|
10
|
+
/** Standard tweet fields to request */
|
|
11
|
+
exports.DEFAULT_TWEET_FIELDS = [
|
|
12
|
+
'author_id',
|
|
13
|
+
'created_at',
|
|
14
|
+
'conversation_id',
|
|
15
|
+
'entities',
|
|
16
|
+
'public_metrics',
|
|
17
|
+
'referenced_tweets',
|
|
18
|
+
'context_annotations',
|
|
19
|
+
'lang',
|
|
20
|
+
'note_tweet',
|
|
21
|
+
];
|
|
22
|
+
/** Standard user fields to request */
|
|
23
|
+
exports.DEFAULT_USER_FIELDS = [
|
|
24
|
+
'name',
|
|
25
|
+
'username',
|
|
26
|
+
'description',
|
|
27
|
+
'profile_image_url',
|
|
28
|
+
'verified',
|
|
29
|
+
'public_metrics',
|
|
30
|
+
'created_at',
|
|
31
|
+
];
|
|
32
|
+
/** Standard expansions for bookmark/tweet endpoints */
|
|
33
|
+
exports.DEFAULT_EXPANSIONS = [
|
|
34
|
+
'author_id',
|
|
35
|
+
'referenced_tweets.id',
|
|
36
|
+
'attachments.media_keys',
|
|
37
|
+
];
|
|
38
|
+
// ============================================================================
|
|
39
|
+
// Errors
|
|
40
|
+
// ============================================================================
|
|
41
|
+
class XApiRequestError extends Error {
|
|
42
|
+
status;
|
|
43
|
+
code;
|
|
44
|
+
rateLimit;
|
|
45
|
+
constructor(message, status, code, rateLimit) {
|
|
46
|
+
super(message);
|
|
47
|
+
this.status = status;
|
|
48
|
+
this.code = code;
|
|
49
|
+
this.rateLimit = rateLimit;
|
|
50
|
+
this.name = 'XApiRequestError';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.XApiRequestError = XApiRequestError;
|
|
54
|
+
class XAuthenticationError extends XApiRequestError {
|
|
55
|
+
constructor(message = 'X API authentication failed — check your Bearer token') {
|
|
56
|
+
super(message, 401, 'AUTHENTICATION_ERROR');
|
|
57
|
+
this.name = 'XAuthenticationError';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
exports.XAuthenticationError = XAuthenticationError;
|
|
61
|
+
class XRateLimitError extends XApiRequestError {
|
|
62
|
+
retryAfter;
|
|
63
|
+
constructor(retryAfter, rateLimit) {
|
|
64
|
+
super(`X API rate limit exceeded — retry after ${retryAfter}s`, 429, 'RATE_LIMIT', rateLimit);
|
|
65
|
+
this.retryAfter = retryAfter;
|
|
66
|
+
this.name = 'XRateLimitError';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
exports.XRateLimitError = XRateLimitError;
|
|
70
|
+
class XNotFoundError extends XApiRequestError {
|
|
71
|
+
constructor(message = 'X API resource not found') {
|
|
72
|
+
super(message, 404, 'NOT_FOUND');
|
|
73
|
+
this.name = 'XNotFoundError';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
exports.XNotFoundError = XNotFoundError;
|
|
77
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/clients/types.ts"],"names":[],"mappings":";AAAA;;GAEG;;;AA4MH,+EAA+E;AAC/E,iBAAiB;AACjB,+EAA+E;AAE/E,uCAAuC;AAC1B,QAAA,oBAAoB,GAAG;IAClC,WAAW;IACX,YAAY;IACZ,iBAAiB;IACjB,UAAU;IACV,gBAAgB;IAChB,mBAAmB;IACnB,qBAAqB;IACrB,MAAM;IACN,YAAY;CACJ,CAAC;AAEX,sCAAsC;AACzB,QAAA,mBAAmB,GAAG;IACjC,MAAM;IACN,UAAU;IACV,aAAa;IACb,mBAAmB;IACnB,UAAU;IACV,gBAAgB;IAChB,YAAY;CACJ,CAAC;AAEX,uDAAuD;AAC1C,QAAA,kBAAkB,GAAG;IAChC,WAAW;IACX,sBAAsB;IACtB,wBAAwB;CAChB,CAAC;AA2BX,+EAA+E;AAC/E,SAAS;AACT,+EAA+E;AAE/E,MAAa,gBAAiB,SAAQ,KAAK;IAGvB;IACA;IACA;IAJlB,YACE,OAAe,EACC,MAAc,EACd,IAAa,EACb,SAIf;QAED,KAAK,CAAC,OAAO,CAAC,CAAC;QARC,WAAM,GAAN,MAAM,CAAQ;QACd,SAAI,GAAJ,IAAI,CAAS;QACb,cAAS,GAAT,SAAS,CAIxB;QAGD,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF;AAdD,4CAcC;AAED,MAAa,oBAAqB,SAAQ,gBAAgB;IACxD,YAAY,OAAO,GAAG,uDAAuD;QAC3E,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,sBAAsB,CAAC,CAAC;QAC5C,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF;AALD,oDAKC;AAED,MAAa,eAAgB,SAAQ,gBAAgB;IAEjC;IADlB,YACkB,UAAkB,EAClC,SAA6D;QAE7D,KAAK,CAAC,2CAA2C,UAAU,GAAG,EAAE,GAAG,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC;QAH9E,eAAU,GAAV,UAAU,CAAQ;QAIlC,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AARD,0CAQC;AAED,MAAa,cAAe,SAAQ,gBAAgB;IAClD,YAAY,OAAO,GAAG,0BAA0B;QAC9C,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,WAAW,CAAC,CAAC;QACjC,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AALD,wCAKC"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* X API v2 client with OAuth 2.0 User Context auth.
|
|
3
|
+
*
|
|
4
|
+
* Bookmarks require User Context (not App-only Bearer token).
|
|
5
|
+
* Uses pay-per-use pricing (launched Feb 2026).
|
|
6
|
+
* Deduplication: same post requested within a 24h UTC window = single charge.
|
|
7
|
+
*/
|
|
8
|
+
import { XAuthConfig, XClientOptions, XApiResponse, Tweet, User, BookmarkFolder, BookmarkListParams, BookmarkFolderListParams } from './types.js';
|
|
9
|
+
export declare class XClient {
|
|
10
|
+
private readonly http;
|
|
11
|
+
private readonly userId;
|
|
12
|
+
private refreshToken?;
|
|
13
|
+
private readonly consumerKey?;
|
|
14
|
+
private readonly consumerSecret?;
|
|
15
|
+
private isRefreshing;
|
|
16
|
+
constructor(auth: XAuthConfig, options?: XClientOptions);
|
|
17
|
+
private canRefresh;
|
|
18
|
+
/**
|
|
19
|
+
* Exchange refresh token for a new access token.
|
|
20
|
+
* Updates .env file with new tokens so they persist across runs.
|
|
21
|
+
*/
|
|
22
|
+
private refreshAccessToken;
|
|
23
|
+
/**
|
|
24
|
+
* Update .env file with new token values.
|
|
25
|
+
* Preserves all other env vars and comments.
|
|
26
|
+
*/
|
|
27
|
+
private updateEnvFile;
|
|
28
|
+
/**
|
|
29
|
+
* Get all bookmarked tweets for the authenticated user.
|
|
30
|
+
* Endpoint: GET /2/users/:id/bookmarks
|
|
31
|
+
* Requires OAuth 2.0 User Context with bookmark.read scope.
|
|
32
|
+
*/
|
|
33
|
+
getBookmarks(params?: BookmarkListParams): Promise<XApiResponse<Tweet[]>>;
|
|
34
|
+
/**
|
|
35
|
+
* Get all bookmarked tweets, auto-paginating through all pages.
|
|
36
|
+
* Caution: each page is a separate API call (pay-per-use charge).
|
|
37
|
+
*/
|
|
38
|
+
getAllBookmarks(params?: Omit<BookmarkListParams, 'pagination_token'>): Promise<{
|
|
39
|
+
tweets: Tweet[];
|
|
40
|
+
users: Map<string, User>;
|
|
41
|
+
}>;
|
|
42
|
+
/**
|
|
43
|
+
* List bookmark folders for the authenticated user.
|
|
44
|
+
* Endpoint: GET /2/users/:id/bookmark_folders
|
|
45
|
+
*/
|
|
46
|
+
getBookmarkFolders(params?: BookmarkFolderListParams): Promise<XApiResponse<BookmarkFolder[]>>;
|
|
47
|
+
/**
|
|
48
|
+
* Get all bookmark folders, auto-paginating.
|
|
49
|
+
*/
|
|
50
|
+
getAllBookmarkFolders(): Promise<BookmarkFolder[]>;
|
|
51
|
+
/**
|
|
52
|
+
* Get tweet IDs from a specific bookmark folder.
|
|
53
|
+
* Endpoint: GET /2/users/:id/bookmarks/folders/:folder_id
|
|
54
|
+
* Note: This endpoint only returns tweet IDs — no text, metrics, or expansions.
|
|
55
|
+
* Use getAllBookmarks() for full tweet data, then cross-reference by ID.
|
|
56
|
+
*/
|
|
57
|
+
getBookmarkFolderTweetIds(folderId: string): Promise<string[]>;
|
|
58
|
+
/**
|
|
59
|
+
* Get full bookmark data with folder membership resolved.
|
|
60
|
+
*
|
|
61
|
+
* Strategy:
|
|
62
|
+
* 1. Fetch all folders (cheap — just names/IDs)
|
|
63
|
+
* 2. Fetch tweet IDs per folder (cheap — just IDs)
|
|
64
|
+
* 3. Fetch all bookmarks with full data from main endpoint (one paginated call)
|
|
65
|
+
* 4. Cross-reference to build folder→tweet mappings
|
|
66
|
+
*
|
|
67
|
+
* This minimizes API cost while getting complete data.
|
|
68
|
+
*/
|
|
69
|
+
getBookmarksWithFolders(): Promise<{
|
|
70
|
+
tweets: Tweet[];
|
|
71
|
+
users: Map<string, User>;
|
|
72
|
+
folders: BookmarkFolder[];
|
|
73
|
+
folderTweetIds: Map<string, string[]>;
|
|
74
|
+
}>;
|
|
75
|
+
/**
|
|
76
|
+
* Look up tweets by ID with full data.
|
|
77
|
+
* Endpoint: GET /2/tweets?ids=...
|
|
78
|
+
* Accepts up to 100 IDs per call.
|
|
79
|
+
*/
|
|
80
|
+
getTweetsByIds(ids: string[]): Promise<{
|
|
81
|
+
tweets: Tweet[];
|
|
82
|
+
users: Map<string, User>;
|
|
83
|
+
}>;
|
|
84
|
+
/**
|
|
85
|
+
* Get the authenticated user's profile.
|
|
86
|
+
* Endpoint: GET /2/users/me
|
|
87
|
+
*/
|
|
88
|
+
getMe(): Promise<XApiResponse<User>>;
|
|
89
|
+
private parseRateLimit;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Create an XClient from environment variables.
|
|
93
|
+
*
|
|
94
|
+
* Required env vars:
|
|
95
|
+
* X_USER_ACCESS_TOKEN — OAuth 2.0 User Access Token (from OAuth flow)
|
|
96
|
+
* X_USER_ID — Authenticated user's numeric ID
|
|
97
|
+
*
|
|
98
|
+
* Auto-refresh env vars (optional but recommended):
|
|
99
|
+
* X_REFRESH_TOKEN — Refresh token for auto-refreshing expired access tokens
|
|
100
|
+
* X_CONSUMER_KEY — App consumer key (client ID)
|
|
101
|
+
* X_CONSUMER_SECRET — App consumer secret
|
|
102
|
+
*
|
|
103
|
+
* If all three refresh vars are set, the client will automatically refresh
|
|
104
|
+
* the access token on 401 and update .env with new tokens.
|
|
105
|
+
*
|
|
106
|
+
* Optional env vars:
|
|
107
|
+
* X_MAX_RETRIES — Max retries on 5xx (default: 3)
|
|
108
|
+
* X_TIMEOUT — Request timeout in ms (default: 30000)
|
|
109
|
+
*/
|
|
110
|
+
export declare function createXClientFromEnv(options?: XClientOptions): XClient;
|
|
111
|
+
//# sourceMappingURL=x-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"x-client.d.ts","sourceRoot":"","sources":["../../src/clients/x-client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,OAAO,EACL,WAAW,EACX,cAAc,EACd,YAAY,EAKZ,KAAK,EACL,IAAI,EACJ,cAAc,EACd,kBAAkB,EAClB,wBAAwB,EAIzB,MAAM,YAAY,CAAC;AAOpB,qBAAa,OAAO;IAClB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgB;IACrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,YAAY,CAAC,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAS;IACzC,OAAO,CAAC,YAAY,CAAS;gBAEjB,IAAI,EAAE,WAAW,EAAE,OAAO,GAAE,cAAmB;IAgG3D,OAAO,CAAC,UAAU;IAIlB;;;OAGG;YACW,kBAAkB;IAuChC;;;OAGG;IACH,OAAO,CAAC,aAAa;IAgCrB;;;;OAIG;IACG,YAAY,CAAC,MAAM,GAAE,kBAAuB,GAAG,OAAO,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC,CAAC;IAsBnF;;;OAGG;IACG,eAAe,CAAC,MAAM,GAAE,IAAI,CAAC,kBAAkB,EAAE,kBAAkB,CAAM,GAAG,OAAO,CAAC;QACxF,MAAM,EAAE,KAAK,EAAE,CAAC;QAChB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;KAC1B,CAAC;IA0BF;;;OAGG;IACG,kBAAkB,CAAC,MAAM,GAAE,wBAA6B,GAAG,OAAO,CAAC,YAAY,CAAC,cAAc,EAAE,CAAC,CAAC;IAWxG;;OAEG;IACG,qBAAqB,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAmBxD;;;;;OAKG;IACG,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAwBpE;;;;;;;;;;OAUG;IACG,uBAAuB,IAAI,OAAO,CAAC;QACvC,MAAM,EAAE,KAAK,EAAE,CAAC;QAChB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QACzB,OAAO,EAAE,cAAc,EAAE,CAAC;QAC1B,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;KACvC,CAAC;IAqBF;;;;OAIG;IACG,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,KAAK,EAAE,CAAC;QAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;KAAE,CAAC;IAmC3F;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IAc1C,OAAO,CAAC,cAAc;CASvB;AAMD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CA6B1E"}
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* X API v2 client with OAuth 2.0 User Context auth.
|
|
4
|
+
*
|
|
5
|
+
* Bookmarks require User Context (not App-only Bearer token).
|
|
6
|
+
* Uses pay-per-use pricing (launched Feb 2026).
|
|
7
|
+
* Deduplication: same post requested within a 24h UTC window = single charge.
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
43
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
44
|
+
};
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.XClient = void 0;
|
|
47
|
+
exports.createXClientFromEnv = createXClientFromEnv;
|
|
48
|
+
const axios_1 = __importDefault(require("axios"));
|
|
49
|
+
const axios_retry_1 = __importDefault(require("axios-retry"));
|
|
50
|
+
const dotenv = __importStar(require("dotenv"));
|
|
51
|
+
const fs = __importStar(require("fs"));
|
|
52
|
+
const path = __importStar(require("path"));
|
|
53
|
+
const types_js_1 = require("./types.js");
|
|
54
|
+
dotenv.config();
|
|
55
|
+
const X_API_BASE = 'https://api.x.com/2';
|
|
56
|
+
const TOKEN_URL = 'https://api.x.com/2/oauth2/token';
|
|
57
|
+
class XClient {
|
|
58
|
+
http;
|
|
59
|
+
userId;
|
|
60
|
+
refreshToken;
|
|
61
|
+
consumerKey;
|
|
62
|
+
consumerSecret;
|
|
63
|
+
isRefreshing = false;
|
|
64
|
+
constructor(auth, options = {}) {
|
|
65
|
+
const { maxRetries = 3, retryDelay = 1000, timeout = 30000 } = options;
|
|
66
|
+
this.userId = auth.userId;
|
|
67
|
+
this.refreshToken = auth.refreshToken;
|
|
68
|
+
this.consumerKey = auth.consumerKey;
|
|
69
|
+
this.consumerSecret = auth.consumerSecret;
|
|
70
|
+
this.http = axios_1.default.create({
|
|
71
|
+
baseURL: X_API_BASE,
|
|
72
|
+
timeout,
|
|
73
|
+
headers: {
|
|
74
|
+
Authorization: `Bearer ${auth.userAccessToken}`,
|
|
75
|
+
'Content-Type': 'application/json',
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
(0, axios_retry_1.default)(this.http, {
|
|
79
|
+
retries: maxRetries,
|
|
80
|
+
retryDelay: (retryCount) => retryCount * retryDelay,
|
|
81
|
+
retryCondition: (error) => {
|
|
82
|
+
const status = error.response?.status;
|
|
83
|
+
// Retry on 5xx and network errors, not on 4xx
|
|
84
|
+
return !status || status >= 500;
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
// Response interceptor: auto-refresh on 401, normalize other errors
|
|
88
|
+
this.http.interceptors.response.use((response) => response, async (error) => {
|
|
89
|
+
if (!error.response) {
|
|
90
|
+
throw new types_js_1.XApiRequestError(`Network error: ${error.message}`, 0, 'NETWORK_ERROR');
|
|
91
|
+
}
|
|
92
|
+
const { status, headers } = error.response;
|
|
93
|
+
const rateLimit = this.parseRateLimit(headers);
|
|
94
|
+
const originalRequest = error.config;
|
|
95
|
+
// Auto-refresh on 401 or 403 if we have a refresh token
|
|
96
|
+
// X returns 403 (not 401) for expired/invalid OAuth user tokens
|
|
97
|
+
if ((status === 401 || status === 403) && this.canRefresh() && !originalRequest._retried) {
|
|
98
|
+
originalRequest._retried = true;
|
|
99
|
+
try {
|
|
100
|
+
const newToken = await this.refreshAccessToken();
|
|
101
|
+
// Update default header for future requests
|
|
102
|
+
this.http.defaults.headers.common['Authorization'] = `Bearer ${newToken}`;
|
|
103
|
+
// Update this request's header and retry
|
|
104
|
+
originalRequest.headers['Authorization'] = `Bearer ${newToken}`;
|
|
105
|
+
return this.http(originalRequest);
|
|
106
|
+
}
|
|
107
|
+
catch (refreshError) {
|
|
108
|
+
throw new types_js_1.XAuthenticationError('Token refresh failed — run `npx tsx scripts/oauth-flow.ts` to re-authenticate');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
switch (status) {
|
|
112
|
+
case 401:
|
|
113
|
+
throw new types_js_1.XAuthenticationError();
|
|
114
|
+
case 403:
|
|
115
|
+
throw new types_js_1.XApiRequestError('X API forbidden — endpoint may require elevated access or OAuth 2.0 User Context', 403, 'FORBIDDEN', rateLimit);
|
|
116
|
+
case 404:
|
|
117
|
+
throw new types_js_1.XNotFoundError();
|
|
118
|
+
case 429: {
|
|
119
|
+
const resetHeader = headers?.['x-rate-limit-reset'];
|
|
120
|
+
const resetEpoch = resetHeader ? Number(resetHeader) : 0;
|
|
121
|
+
const retryAfter = Math.max(1, resetEpoch - Math.floor(Date.now() / 1000));
|
|
122
|
+
throw new types_js_1.XRateLimitError(retryAfter, rateLimit);
|
|
123
|
+
}
|
|
124
|
+
default:
|
|
125
|
+
throw new types_js_1.XApiRequestError(`X API error ${status}: ${JSON.stringify(error.response.data)}`, status, undefined, rateLimit);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
// ==========================================================================
|
|
130
|
+
// Token Refresh
|
|
131
|
+
// ==========================================================================
|
|
132
|
+
canRefresh() {
|
|
133
|
+
return !this.isRefreshing && !!this.refreshToken && !!this.consumerKey && !!this.consumerSecret;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Exchange refresh token for a new access token.
|
|
137
|
+
* Updates .env file with new tokens so they persist across runs.
|
|
138
|
+
*/
|
|
139
|
+
async refreshAccessToken() {
|
|
140
|
+
this.isRefreshing = true;
|
|
141
|
+
try {
|
|
142
|
+
const basicAuth = Buffer.from(`${this.consumerKey}:${this.consumerSecret}`).toString('base64');
|
|
143
|
+
const params = new URLSearchParams({
|
|
144
|
+
grant_type: 'refresh_token',
|
|
145
|
+
refresh_token: this.refreshToken,
|
|
146
|
+
client_id: this.consumerKey,
|
|
147
|
+
});
|
|
148
|
+
const response = await axios_1.default.post(TOKEN_URL, params.toString(), {
|
|
149
|
+
headers: {
|
|
150
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
151
|
+
Authorization: `Basic ${basicAuth}`,
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
const { access_token, refresh_token: newRefreshToken } = response.data;
|
|
155
|
+
// Update .env file so new tokens persist
|
|
156
|
+
this.updateEnvFile(access_token, newRefreshToken);
|
|
157
|
+
// Update in-memory env vars
|
|
158
|
+
process.env.X_USER_ACCESS_TOKEN = access_token;
|
|
159
|
+
if (newRefreshToken) {
|
|
160
|
+
process.env.X_REFRESH_TOKEN = newRefreshToken;
|
|
161
|
+
// X uses rotating refresh tokens — the old one is now invalid
|
|
162
|
+
this.refreshToken = newRefreshToken;
|
|
163
|
+
}
|
|
164
|
+
console.info('[@hidden-leaf/x-skill] Access token refreshed successfully');
|
|
165
|
+
return access_token;
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
this.isRefreshing = false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Update .env file with new token values.
|
|
173
|
+
* Preserves all other env vars and comments.
|
|
174
|
+
*/
|
|
175
|
+
updateEnvFile(accessToken, refreshToken) {
|
|
176
|
+
const envPath = path.resolve(process.cwd(), '.env');
|
|
177
|
+
try {
|
|
178
|
+
if (!fs.existsSync(envPath))
|
|
179
|
+
return;
|
|
180
|
+
let content = fs.readFileSync(envPath, 'utf8');
|
|
181
|
+
// Replace access token
|
|
182
|
+
content = content.replace(/^X_USER_ACCESS_TOKEN=.*/m, `X_USER_ACCESS_TOKEN=${accessToken}`);
|
|
183
|
+
// Replace refresh token if we got a new one (rotating tokens)
|
|
184
|
+
if (refreshToken) {
|
|
185
|
+
content = content.replace(/^X_REFRESH_TOKEN=.*/m, `X_REFRESH_TOKEN=${refreshToken}`);
|
|
186
|
+
}
|
|
187
|
+
fs.writeFileSync(envPath, content, 'utf8');
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
// Non-fatal — tokens still work in memory for this session
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// ==========================================================================
|
|
194
|
+
// Bookmarks
|
|
195
|
+
// ==========================================================================
|
|
196
|
+
/**
|
|
197
|
+
* Get all bookmarked tweets for the authenticated user.
|
|
198
|
+
* Endpoint: GET /2/users/:id/bookmarks
|
|
199
|
+
* Requires OAuth 2.0 User Context with bookmark.read scope.
|
|
200
|
+
*/
|
|
201
|
+
async getBookmarks(params = {}) {
|
|
202
|
+
const { max_results = 100, pagination_token, tweet_fields = [...types_js_1.DEFAULT_TWEET_FIELDS], user_fields = [...types_js_1.DEFAULT_USER_FIELDS], expansions = [...types_js_1.DEFAULT_EXPANSIONS], } = params;
|
|
203
|
+
const response = await this.http.get(`/users/${this.userId}/bookmarks`, {
|
|
204
|
+
params: {
|
|
205
|
+
max_results,
|
|
206
|
+
pagination_token,
|
|
207
|
+
'tweet.fields': tweet_fields.join(','),
|
|
208
|
+
'user.fields': user_fields.join(','),
|
|
209
|
+
expansions: expansions.join(','),
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
return response.data;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Get all bookmarked tweets, auto-paginating through all pages.
|
|
216
|
+
* Caution: each page is a separate API call (pay-per-use charge).
|
|
217
|
+
*/
|
|
218
|
+
async getAllBookmarks(params = {}) {
|
|
219
|
+
const tweets = [];
|
|
220
|
+
const users = new Map();
|
|
221
|
+
let nextToken;
|
|
222
|
+
do {
|
|
223
|
+
const response = await this.getBookmarks({
|
|
224
|
+
...params,
|
|
225
|
+
pagination_token: nextToken,
|
|
226
|
+
});
|
|
227
|
+
if (response.data) {
|
|
228
|
+
tweets.push(...response.data);
|
|
229
|
+
}
|
|
230
|
+
if (response.includes?.users) {
|
|
231
|
+
for (const user of response.includes.users) {
|
|
232
|
+
users.set(user.id, user);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
nextToken = response.meta?.next_token;
|
|
236
|
+
} while (nextToken);
|
|
237
|
+
return { tweets, users };
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* List bookmark folders for the authenticated user.
|
|
241
|
+
* Endpoint: GET /2/users/:id/bookmark_folders
|
|
242
|
+
*/
|
|
243
|
+
async getBookmarkFolders(params = {}) {
|
|
244
|
+
const response = await this.http.get(`/users/${this.userId}/bookmarks/folders`, {
|
|
245
|
+
params: {
|
|
246
|
+
max_results: params.max_results,
|
|
247
|
+
pagination_token: params.pagination_token,
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
return response.data;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Get all bookmark folders, auto-paginating.
|
|
254
|
+
*/
|
|
255
|
+
async getAllBookmarkFolders() {
|
|
256
|
+
const folders = [];
|
|
257
|
+
let nextToken;
|
|
258
|
+
do {
|
|
259
|
+
const response = await this.getBookmarkFolders({
|
|
260
|
+
pagination_token: nextToken,
|
|
261
|
+
});
|
|
262
|
+
if (response.data) {
|
|
263
|
+
folders.push(...response.data);
|
|
264
|
+
}
|
|
265
|
+
nextToken = response.meta?.next_token;
|
|
266
|
+
} while (nextToken);
|
|
267
|
+
return folders;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Get tweet IDs from a specific bookmark folder.
|
|
271
|
+
* Endpoint: GET /2/users/:id/bookmarks/folders/:folder_id
|
|
272
|
+
* Note: This endpoint only returns tweet IDs — no text, metrics, or expansions.
|
|
273
|
+
* Use getAllBookmarks() for full tweet data, then cross-reference by ID.
|
|
274
|
+
*/
|
|
275
|
+
async getBookmarkFolderTweetIds(folderId) {
|
|
276
|
+
const ids = [];
|
|
277
|
+
let nextToken;
|
|
278
|
+
do {
|
|
279
|
+
const response = await this.http.get(`/users/${this.userId}/bookmarks/folders/${folderId}`, {
|
|
280
|
+
params: {
|
|
281
|
+
...(nextToken ? { pagination_token: nextToken } : {}),
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
const data = response.data;
|
|
285
|
+
if (data.data) {
|
|
286
|
+
ids.push(...data.data.map((t) => t.id));
|
|
287
|
+
}
|
|
288
|
+
nextToken = data.meta?.next_token;
|
|
289
|
+
} while (nextToken);
|
|
290
|
+
return ids;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Get full bookmark data with folder membership resolved.
|
|
294
|
+
*
|
|
295
|
+
* Strategy:
|
|
296
|
+
* 1. Fetch all folders (cheap — just names/IDs)
|
|
297
|
+
* 2. Fetch tweet IDs per folder (cheap — just IDs)
|
|
298
|
+
* 3. Fetch all bookmarks with full data from main endpoint (one paginated call)
|
|
299
|
+
* 4. Cross-reference to build folder→tweet mappings
|
|
300
|
+
*
|
|
301
|
+
* This minimizes API cost while getting complete data.
|
|
302
|
+
*/
|
|
303
|
+
async getBookmarksWithFolders() {
|
|
304
|
+
// Step 1: Get folders
|
|
305
|
+
const folders = await this.getAllBookmarkFolders();
|
|
306
|
+
// Step 2: Get tweet IDs per folder
|
|
307
|
+
const folderTweetIds = new Map();
|
|
308
|
+
for (const folder of folders) {
|
|
309
|
+
const ids = await this.getBookmarkFolderTweetIds(folder.id);
|
|
310
|
+
folderTweetIds.set(folder.id, ids);
|
|
311
|
+
}
|
|
312
|
+
// Step 3: Get all bookmarks with full data
|
|
313
|
+
const { tweets, users } = await this.getAllBookmarks();
|
|
314
|
+
return { tweets, users, folders, folderTweetIds };
|
|
315
|
+
}
|
|
316
|
+
// ==========================================================================
|
|
317
|
+
// Tweet Lookup (hydration)
|
|
318
|
+
// ==========================================================================
|
|
319
|
+
/**
|
|
320
|
+
* Look up tweets by ID with full data.
|
|
321
|
+
* Endpoint: GET /2/tweets?ids=...
|
|
322
|
+
* Accepts up to 100 IDs per call.
|
|
323
|
+
*/
|
|
324
|
+
async getTweetsByIds(ids) {
|
|
325
|
+
const tweets = [];
|
|
326
|
+
const users = new Map();
|
|
327
|
+
// Batch in chunks of 100
|
|
328
|
+
for (let i = 0; i < ids.length; i += 100) {
|
|
329
|
+
const batch = ids.slice(i, i + 100);
|
|
330
|
+
const response = await this.http.get('/tweets', {
|
|
331
|
+
params: {
|
|
332
|
+
ids: batch.join(','),
|
|
333
|
+
'tweet.fields': [...types_js_1.DEFAULT_TWEET_FIELDS].join(','),
|
|
334
|
+
'user.fields': [...types_js_1.DEFAULT_USER_FIELDS].join(','),
|
|
335
|
+
expansions: [...types_js_1.DEFAULT_EXPANSIONS].join(','),
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
const data = response.data;
|
|
339
|
+
if (data.data) {
|
|
340
|
+
tweets.push(...data.data);
|
|
341
|
+
}
|
|
342
|
+
if (data.includes?.users) {
|
|
343
|
+
for (const user of data.includes.users) {
|
|
344
|
+
users.set(user.id, user);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return { tweets, users };
|
|
349
|
+
}
|
|
350
|
+
// ==========================================================================
|
|
351
|
+
// User Lookup
|
|
352
|
+
// ==========================================================================
|
|
353
|
+
/**
|
|
354
|
+
* Get the authenticated user's profile.
|
|
355
|
+
* Endpoint: GET /2/users/me
|
|
356
|
+
*/
|
|
357
|
+
async getMe() {
|
|
358
|
+
const response = await this.http.get('/users/me', {
|
|
359
|
+
params: {
|
|
360
|
+
'user.fields': [...types_js_1.DEFAULT_USER_FIELDS].join(','),
|
|
361
|
+
},
|
|
362
|
+
});
|
|
363
|
+
return response.data;
|
|
364
|
+
}
|
|
365
|
+
// ==========================================================================
|
|
366
|
+
// Utilities
|
|
367
|
+
// ==========================================================================
|
|
368
|
+
parseRateLimit(headers) {
|
|
369
|
+
const limit = Number(headers['x-rate-limit-limit']);
|
|
370
|
+
const remaining = Number(headers['x-rate-limit-remaining']);
|
|
371
|
+
const reset = new Date(Number(headers['x-rate-limit-reset']) * 1000);
|
|
372
|
+
if (isNaN(limit))
|
|
373
|
+
return undefined;
|
|
374
|
+
return { limit, remaining, reset };
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
exports.XClient = XClient;
|
|
378
|
+
// ============================================================================
|
|
379
|
+
// Factory
|
|
380
|
+
// ============================================================================
|
|
381
|
+
/**
|
|
382
|
+
* Create an XClient from environment variables.
|
|
383
|
+
*
|
|
384
|
+
* Required env vars:
|
|
385
|
+
* X_USER_ACCESS_TOKEN — OAuth 2.0 User Access Token (from OAuth flow)
|
|
386
|
+
* X_USER_ID — Authenticated user's numeric ID
|
|
387
|
+
*
|
|
388
|
+
* Auto-refresh env vars (optional but recommended):
|
|
389
|
+
* X_REFRESH_TOKEN — Refresh token for auto-refreshing expired access tokens
|
|
390
|
+
* X_CONSUMER_KEY — App consumer key (client ID)
|
|
391
|
+
* X_CONSUMER_SECRET — App consumer secret
|
|
392
|
+
*
|
|
393
|
+
* If all three refresh vars are set, the client will automatically refresh
|
|
394
|
+
* the access token on 401 and update .env with new tokens.
|
|
395
|
+
*
|
|
396
|
+
* Optional env vars:
|
|
397
|
+
* X_MAX_RETRIES — Max retries on 5xx (default: 3)
|
|
398
|
+
* X_TIMEOUT — Request timeout in ms (default: 30000)
|
|
399
|
+
*/
|
|
400
|
+
function createXClientFromEnv(options = {}) {
|
|
401
|
+
const userAccessToken = process.env.X_USER_ACCESS_TOKEN;
|
|
402
|
+
const userId = process.env.X_USER_ID;
|
|
403
|
+
if (!userAccessToken) {
|
|
404
|
+
throw new types_js_1.XAuthenticationError('X_USER_ACCESS_TOKEN not set — run `npx tsx scripts/oauth-flow.ts` to generate one');
|
|
405
|
+
}
|
|
406
|
+
if (!userId) {
|
|
407
|
+
throw new types_js_1.XAuthenticationError('X_USER_ID not set — run `npx tsx scripts/oauth-flow.ts` to get your user ID');
|
|
408
|
+
}
|
|
409
|
+
return new XClient({
|
|
410
|
+
userAccessToken,
|
|
411
|
+
userId,
|
|
412
|
+
refreshToken: process.env.X_REFRESH_TOKEN,
|
|
413
|
+
consumerKey: process.env.X_CONSUMER_KEY,
|
|
414
|
+
consumerSecret: process.env.X_CONSUMER_SECRET,
|
|
415
|
+
}, {
|
|
416
|
+
maxRetries: Number(process.env.X_MAX_RETRIES) || options.maxRetries,
|
|
417
|
+
timeout: Number(process.env.X_TIMEOUT) || options.timeout,
|
|
418
|
+
...options,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
//# sourceMappingURL=x-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"x-client.js","sourceRoot":"","sources":["../../src/clients/x-client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hidden-leaf/x-skill
|
|
3
|
+
*
|
|
4
|
+
* X (Twitter) bookmark intelligence skill for Claude Code.
|
|
5
|
+
* Turns curated X bookmarks into structured research briefs.
|
|
6
|
+
*
|
|
7
|
+
* v1: Bookmark intelligence (list, fetch, brief)
|
|
8
|
+
* v2: Search, threads, profile (planned)
|
|
9
|
+
* v3: Publish, schedule, reply (planned)
|
|
10
|
+
*/
|
|
11
|
+
export { XClient, createXClientFromEnv } from './clients/x-client.js';
|
|
12
|
+
export type { XAuthConfig, XApiResponse, XPaginationMeta, XApiError, XClientOptions, Tweet, TweetPublicMetrics, TweetEntities, TweetUrl, TweetMention, TweetHashtag, TweetAnnotation, ReferencedTweet, TweetAttachments, ContextAnnotation, NoteTweet, User, UserPublicMetrics, Media, XIncludes, BookmarkFolder, BookmarkListParams, BookmarkFolderListParams, BookmarkFolderTweetsParams, } from './clients/types.js';
|
|
13
|
+
export { XApiRequestError, XAuthenticationError, XRateLimitError, XNotFoundError, DEFAULT_TWEET_FIELDS, DEFAULT_USER_FIELDS, DEFAULT_EXPANSIONS, } from './clients/types.js';
|
|
14
|
+
export { BookmarksSkill, createBookmarksSkillFromEnv } from './skills/bookmarks/index.js';
|
|
15
|
+
export { buildBriefPrompt, generateBrief } from './skills/bookmarks/synthesize.js';
|
|
16
|
+
export { BookmarkStore, createStoreFromEnv } from './cache/store.js';
|
|
17
|
+
export type { EnrichedBookmark, BookmarkFolderContents, BriefOptions, ResearchBrief, BookmarkListOutput, BookmarkFetchOutput, BookmarkBriefOutput, SyncResult, } from './skills/bookmarks/types.js';
|
|
18
|
+
export { createLogger } from './utils/logger.js';
|
|
19
|
+
export declare const VERSION = "1.0.0";
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,OAAO,EAAE,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAMtE,YAAY,EACV,WAAW,EACX,YAAY,EACZ,eAAe,EACf,SAAS,EACT,cAAc,EACd,KAAK,EACL,kBAAkB,EAClB,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,SAAS,EACT,IAAI,EACJ,iBAAiB,EACjB,KAAK,EACL,SAAS,EACT,cAAc,EACd,kBAAkB,EAClB,wBAAwB,EACxB,0BAA0B,GAC3B,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,eAAe,EACf,cAAc,EACd,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAM5B,OAAO,EAAE,cAAc,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAC;AAC1F,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAMnF,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAMrE,YAAY,EACV,gBAAgB,EAChB,sBAAsB,EACtB,YAAY,EACZ,aAAa,EACb,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,UAAU,GACX,MAAM,6BAA6B,CAAC;AAMrC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAMjD,eAAO,MAAM,OAAO,UAAU,CAAC"}
|