@lucianfialho/build-in-public-mcp 0.3.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/LICENSE +21 -0
- package/README.md +218 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +425 -0
- package/dist/index.js.map +1 -0
- package/dist/services/git-analyzer.d.ts +22 -0
- package/dist/services/git-analyzer.d.ts.map +1 -0
- package/dist/services/git-analyzer.js +168 -0
- package/dist/services/git-analyzer.js.map +1 -0
- package/dist/services/storage.d.ts +111 -0
- package/dist/services/storage.d.ts.map +1 -0
- package/dist/services/storage.js +260 -0
- package/dist/services/storage.js.map +1 -0
- package/dist/services/suggestion-engine.d.ts +20 -0
- package/dist/services/suggestion-engine.d.ts.map +1 -0
- package/dist/services/suggestion-engine.js +171 -0
- package/dist/services/suggestion-engine.js.map +1 -0
- package/dist/services/twitter.d.ts +36 -0
- package/dist/services/twitter.d.ts.map +1 -0
- package/dist/services/twitter.js +149 -0
- package/dist/services/twitter.js.map +1 -0
- package/dist/utils/oauth.d.ts +15 -0
- package/dist/utils/oauth.d.ts.map +1 -0
- package/dist/utils/oauth.js +150 -0
- package/dist/utils/oauth.js.map +1 -0
- package/package.json +42 -0
- package/server.json +37 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Suggestion Engine for Build in Public
|
|
4
|
+
* Analyzes session context and generates intelligent tweet suggestions
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.generateSuggestions = generateSuggestions;
|
|
8
|
+
exports.scoreConfidence = scoreConfidence;
|
|
9
|
+
/**
|
|
10
|
+
* Generate tweet suggestions based on session context
|
|
11
|
+
*/
|
|
12
|
+
function generateSuggestions(context) {
|
|
13
|
+
const suggestions = [];
|
|
14
|
+
// Strategy 1: Commit-based suggestions
|
|
15
|
+
if (context.commits && context.commits.length > 0) {
|
|
16
|
+
const commitSuggestion = createCommitSuggestion(context.commits[context.commits.length - 1]);
|
|
17
|
+
if (commitSuggestion) {
|
|
18
|
+
suggestions.push(commitSuggestion);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// Strategy 2: Achievement-based suggestions
|
|
22
|
+
if (context.achievements && context.achievements.length > 0) {
|
|
23
|
+
const achievementSuggestion = createAchievementSuggestion(context);
|
|
24
|
+
if (achievementSuggestion) {
|
|
25
|
+
suggestions.push(achievementSuggestion);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// Strategy 3: Learning-based suggestions
|
|
29
|
+
if (context.learnings && context.learnings.length > 0) {
|
|
30
|
+
const learningSuggestion = createLearningSuggestion(context);
|
|
31
|
+
if (learningSuggestion) {
|
|
32
|
+
suggestions.push(learningSuggestion);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Strategy 4: Session summary
|
|
36
|
+
if (context.filesModified && context.filesModified.length >= 3) {
|
|
37
|
+
const sessionSuggestion = createSessionSuggestion(context);
|
|
38
|
+
if (sessionSuggestion) {
|
|
39
|
+
suggestions.push(sessionSuggestion);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Sort by confidence (highest first)
|
|
43
|
+
return suggestions.sort((a, b) => b.confidence - a.confidence);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Calculate confidence score for suggestions
|
|
47
|
+
*/
|
|
48
|
+
function scoreConfidence(context) {
|
|
49
|
+
let score = 0;
|
|
50
|
+
// Explicit triggers (high confidence)
|
|
51
|
+
if (context.customMessage?.includes('#thread')) {
|
|
52
|
+
score += 0.8;
|
|
53
|
+
}
|
|
54
|
+
if (context.triggerMessage?.match(/(done|finished|terminei|concluí)/i)) {
|
|
55
|
+
score += 0.6;
|
|
56
|
+
}
|
|
57
|
+
// Implicit signals (medium confidence)
|
|
58
|
+
if (context.commits && context.commits.length > 0) {
|
|
59
|
+
score += 0.4;
|
|
60
|
+
}
|
|
61
|
+
if (context.filesModified && context.filesModified.length >= 3) {
|
|
62
|
+
score += 0.3;
|
|
63
|
+
}
|
|
64
|
+
if (context.achievements && context.achievements.length > 0) {
|
|
65
|
+
score += 0.5;
|
|
66
|
+
}
|
|
67
|
+
// Recency boost (recent activity is more relevant)
|
|
68
|
+
const timeSinceStart = Date.now() - new Date(context.startTime).getTime();
|
|
69
|
+
if (timeSinceStart < 2 * 60 * 60 * 1000) {
|
|
70
|
+
// Within 2 hours
|
|
71
|
+
score += 0.2;
|
|
72
|
+
}
|
|
73
|
+
return Math.min(score, 1.0);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Create suggestion based on git commit
|
|
77
|
+
*/
|
|
78
|
+
function createCommitSuggestion(commit) {
|
|
79
|
+
if (!commit || !commit.message) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const filesCount = commit.filesChanged?.length || 0;
|
|
83
|
+
const filesText = filesCount === 1 ? '1 file' : `${filesCount} files`;
|
|
84
|
+
let message = `Just committed: "${commit.message}"\n\n`;
|
|
85
|
+
message += `Modified ${filesText}. `;
|
|
86
|
+
if (commit.additions !== undefined || commit.deletions !== undefined) {
|
|
87
|
+
message += `+${commit.additions || 0}/-${commit.deletions || 0} lines. `;
|
|
88
|
+
}
|
|
89
|
+
message += `#BuildInPublic #Coding`;
|
|
90
|
+
// Trim to 280 chars
|
|
91
|
+
if (message.length > 280) {
|
|
92
|
+
message = message.substring(0, 277) + '...';
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
message,
|
|
96
|
+
confidence: 0.85,
|
|
97
|
+
reason: 'Recent git commit detected',
|
|
98
|
+
type: 'commit',
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Create suggestion based on achievements
|
|
103
|
+
*/
|
|
104
|
+
function createAchievementSuggestion(context) {
|
|
105
|
+
if (!context.achievements || context.achievements.length === 0) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
const topAchievements = context.achievements.slice(0, 3);
|
|
109
|
+
let message = '✅ Progress update:\n\n';
|
|
110
|
+
topAchievements.forEach((achievement, index) => {
|
|
111
|
+
message += `${index + 1}. ${achievement}\n`;
|
|
112
|
+
});
|
|
113
|
+
message += `\n#BuildInPublic`;
|
|
114
|
+
if (message.length > 280) {
|
|
115
|
+
message = message.substring(0, 277) + '...';
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
message,
|
|
119
|
+
confidence: 0.75,
|
|
120
|
+
reason: `${context.achievements.length} achievements logged`,
|
|
121
|
+
type: 'achievement',
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Create suggestion based on learnings
|
|
126
|
+
*/
|
|
127
|
+
function createLearningSuggestion(context) {
|
|
128
|
+
if (!context.learnings || context.learnings.length === 0) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const learning = context.learnings[0]; // Take first/most recent
|
|
132
|
+
let message = `💡 TIL (Today I Learned):\n\n${learning}\n\n#BuildInPublic #Learning`;
|
|
133
|
+
if (message.length > 280) {
|
|
134
|
+
message = message.substring(0, 277) + '...';
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
message,
|
|
138
|
+
confidence: 0.7,
|
|
139
|
+
reason: 'Learning moment captured',
|
|
140
|
+
type: 'learning',
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Create suggestion based on session summary
|
|
145
|
+
*/
|
|
146
|
+
function createSessionSuggestion(context) {
|
|
147
|
+
const filesCount = context.filesModified?.length || 0;
|
|
148
|
+
const toolsCount = context.toolsUsed?.length || 0;
|
|
149
|
+
if (filesCount === 0) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
let message = `Wrapped up a coding session! 🚀\n\n`;
|
|
153
|
+
message += `Modified ${filesCount} files `;
|
|
154
|
+
if (toolsCount > 0) {
|
|
155
|
+
message += `using ${toolsCount} different tools. `;
|
|
156
|
+
}
|
|
157
|
+
if (context.challenges && context.challenges.length > 0) {
|
|
158
|
+
message += `\n\nKey challenge: ${context.challenges[0]}`;
|
|
159
|
+
}
|
|
160
|
+
message += `\n\n#BuildInPublic`;
|
|
161
|
+
if (message.length > 280) {
|
|
162
|
+
message = message.substring(0, 277) + '...';
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
message,
|
|
166
|
+
confidence: 0.6,
|
|
167
|
+
reason: `Active session with ${filesCount} files modified`,
|
|
168
|
+
type: 'session',
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=suggestion-engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"suggestion-engine.js","sourceRoot":"","sources":["../../src/services/suggestion-engine.ts"],"names":[],"mappings":";AAAA;;;GAGG;;AAcH,kDAuCC;AAKD,0CA8BC;AA7ED;;GAEG;AACH,SAAgB,mBAAmB,CAAC,OAAuB;IACzD,MAAM,WAAW,GAAiB,EAAE,CAAC;IAErC,uCAAuC;IACvC,IAAI,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClD,MAAM,gBAAgB,GAAG,sBAAsB,CAC7C,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAC5C,CAAC;QACF,IAAI,gBAAgB,EAAE,CAAC;YACrB,WAAW,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QACrC,CAAC;IACH,CAAC;IAED,4CAA4C;IAC5C,IAAI,OAAO,CAAC,YAAY,IAAI,OAAO,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5D,MAAM,qBAAqB,GAAG,2BAA2B,CAAC,OAAO,CAAC,CAAC;QACnE,IAAI,qBAAqB,EAAE,CAAC;YAC1B,WAAW,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;IAED,yCAAyC;IACzC,IAAI,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtD,MAAM,kBAAkB,GAAG,wBAAwB,CAAC,OAAO,CAAC,CAAC;QAC7D,IAAI,kBAAkB,EAAE,CAAC;YACvB,WAAW,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;IAED,8BAA8B;IAC9B,IAAI,OAAO,CAAC,aAAa,IAAI,OAAO,CAAC,aAAa,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;QAC/D,MAAM,iBAAiB,GAAG,uBAAuB,CAAC,OAAO,CAAC,CAAC;QAC3D,IAAI,iBAAiB,EAAE,CAAC;YACtB,WAAW,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;IAED,qCAAqC;IACrC,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC;AACjE,CAAC;AAED;;GAEG;AACH,SAAgB,eAAe,CAAC,OAAuB;IACrD,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,sCAAsC;IACtC,IAAI,OAAO,CAAC,aAAa,EAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QAC/C,KAAK,IAAI,GAAG,CAAC;IACf,CAAC;IACD,IAAI,OAAO,CAAC,cAAc,EAAE,KAAK,CAAC,mCAAmC,CAAC,EAAE,CAAC;QACvE,KAAK,IAAI,GAAG,CAAC;IACf,CAAC;IAED,uCAAuC;IACvC,IAAI,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClD,KAAK,IAAI,GAAG,CAAC;IACf,CAAC;IACD,IAAI,OAAO,CAAC,aAAa,IAAI,OAAO,CAAC,aAAa,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;QAC/D,KAAK,IAAI,GAAG,CAAC;IACf,CAAC;IACD,IAAI,OAAO,CAAC,YAAY,IAAI,OAAO,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5D,KAAK,IAAI,GAAG,CAAC;IACf,CAAC;IAED,mDAAmD;IACnD,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC;IAC1E,IAAI,cAAc,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC;QACxC,iBAAiB;QACjB,KAAK,IAAI,GAAG,CAAC;IACf,CAAC;IAED,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AAC9B,CAAC;AAED;;GAEG;AACH,SAAS,sBAAsB,CAAC,MAAiB;IAC/C,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QAC/B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,CAAC,YAAY,EAAE,MAAM,IAAI,CAAC,CAAC;IACpD,MAAM,SAAS,GAAG,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,UAAU,QAAQ,CAAC;IAEtE,IAAI,OAAO,GAAG,oBAAoB,MAAM,CAAC,OAAO,OAAO,CAAC;IACxD,OAAO,IAAI,YAAY,SAAS,IAAI,CAAC;IAErC,IAAI,MAAM,CAAC,SAAS,KAAK,SAAS,IAAI,MAAM,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;QACrE,OAAO,IAAI,IAAI,MAAM,CAAC,SAAS,IAAI,CAAC,KAAK,MAAM,CAAC,SAAS,IAAI,CAAC,UAAU,CAAC;IAC3E,CAAC;IAED,OAAO,IAAI,wBAAwB,CAAC;IAEpC,oBAAoB;IACpB,IAAI,OAAO,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;QACzB,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC;IAC9C,CAAC;IAED,OAAO;QACL,OAAO;QACP,UAAU,EAAE,IAAI;QAChB,MAAM,EAAE,4BAA4B;QACpC,IAAI,EAAE,QAAQ;KACf,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,SAAS,2BAA2B,CAClC,OAAuB;IAEvB,IAAI,CAAC,OAAO,CAAC,YAAY,IAAI,OAAO,CAAC,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/D,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,eAAe,GAAG,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACzD,IAAI,OAAO,GAAG,wBAAwB,CAAC;IAEvC,eAAe,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,KAAK,EAAE,EAAE;QAC7C,OAAO,IAAI,GAAG,KAAK,GAAG,CAAC,KAAK,WAAW,IAAI,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,OAAO,IAAI,kBAAkB,CAAC;IAE9B,IAAI,OAAO,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;QACzB,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC;IAC9C,CAAC;IAED,OAAO;QACL,OAAO;QACP,UAAU,EAAE,IAAI;QAChB,MAAM,EAAE,GAAG,OAAO,CAAC,YAAY,CAAC,MAAM,sBAAsB;QAC5D,IAAI,EAAE,aAAa;KACpB,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,SAAS,wBAAwB,CAAC,OAAuB;IACvD,IAAI,CAAC,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,yBAAyB;IAChE,IAAI,OAAO,GAAG,gCAAgC,QAAQ,8BAA8B,CAAC;IAErF,IAAI,OAAO,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;QACzB,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC;IAC9C,CAAC;IAED,OAAO;QACL,OAAO;QACP,UAAU,EAAE,GAAG;QACf,MAAM,EAAE,0BAA0B;QAClC,IAAI,EAAE,UAAU;KACjB,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,SAAS,uBAAuB,CAAC,OAAuB;IACtD,MAAM,UAAU,GAAG,OAAO,CAAC,aAAa,EAAE,MAAM,IAAI,CAAC,CAAC;IACtD,MAAM,UAAU,GAAG,OAAO,CAAC,SAAS,EAAE,MAAM,IAAI,CAAC,CAAC;IAElD,IAAI,UAAU,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,OAAO,GAAG,qCAAqC,CAAC;IACpD,OAAO,IAAI,YAAY,UAAU,SAAS,CAAC;IAE3C,IAAI,UAAU,GAAG,CAAC,EAAE,CAAC;QACnB,OAAO,IAAI,SAAS,UAAU,oBAAoB,CAAC;IACrD,CAAC;IAED,IAAI,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxD,OAAO,IAAI,sBAAsB,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3D,CAAC;IAED,OAAO,IAAI,oBAAoB,CAAC;IAEhC,IAAI,OAAO,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;QACzB,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC;IAC9C,CAAC;IAED,OAAO;QACL,OAAO;QACP,UAAU,EAAE,GAAG;QACf,MAAM,EAAE,uBAAuB,UAAU,iBAAiB;QAC1D,IAAI,EAAE,SAAS;KAChB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Twitter API service
|
|
3
|
+
* Handles tweet posting, thread creation, and API interactions
|
|
4
|
+
*/
|
|
5
|
+
import { TwitterApi } from 'twitter-api-v2';
|
|
6
|
+
/**
|
|
7
|
+
* Get authenticated Twitter client
|
|
8
|
+
*/
|
|
9
|
+
export declare function getTwitterClient(): TwitterApi | null;
|
|
10
|
+
/**
|
|
11
|
+
* Check if user is authenticated
|
|
12
|
+
*/
|
|
13
|
+
export declare function isAuthenticated(): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Post a single tweet
|
|
16
|
+
*/
|
|
17
|
+
export declare function postTweet(message: string): Promise<{
|
|
18
|
+
id: string;
|
|
19
|
+
url: string;
|
|
20
|
+
}>;
|
|
21
|
+
/**
|
|
22
|
+
* Create a Twitter thread
|
|
23
|
+
* Posts multiple tweets in reply chain
|
|
24
|
+
*/
|
|
25
|
+
export declare function postThread(messages: string[], replyToTweetId?: string): Promise<{
|
|
26
|
+
ids: string[];
|
|
27
|
+
urls: string[];
|
|
28
|
+
}>;
|
|
29
|
+
/**
|
|
30
|
+
* Verify API credentials work
|
|
31
|
+
*/
|
|
32
|
+
export declare function verifyCredentials(): Promise<{
|
|
33
|
+
username: string;
|
|
34
|
+
name: string;
|
|
35
|
+
}>;
|
|
36
|
+
//# sourceMappingURL=twitter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"twitter.d.ts","sourceRoot":"","sources":["../../src/services/twitter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAS5C;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,UAAU,GAAG,IAAI,CAWpD;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAED;;GAEG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IACxD,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;CACb,CAAC,CA+CD;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,MAAM,EAAE,EAClB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC;IACT,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,CAAC,CAkED;AAED;;GAEG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC,CAgBD"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Twitter API service
|
|
4
|
+
* Handles tweet posting, thread creation, and API interactions
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.getTwitterClient = getTwitterClient;
|
|
8
|
+
exports.isAuthenticated = isAuthenticated;
|
|
9
|
+
exports.postTweet = postTweet;
|
|
10
|
+
exports.postThread = postThread;
|
|
11
|
+
exports.verifyCredentials = verifyCredentials;
|
|
12
|
+
const storage_js_1 = require("./storage.js");
|
|
13
|
+
const oauth_js_1 = require("../utils/oauth.js");
|
|
14
|
+
/**
|
|
15
|
+
* Get authenticated Twitter client
|
|
16
|
+
*/
|
|
17
|
+
function getTwitterClient() {
|
|
18
|
+
if (!(0, storage_js_1.hasAuthTokens)()) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const tokens = (0, storage_js_1.loadAuthTokens)();
|
|
22
|
+
if (!tokens) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
return (0, oauth_js_1.createTwitterClient)(tokens);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Check if user is authenticated
|
|
29
|
+
*/
|
|
30
|
+
function isAuthenticated() {
|
|
31
|
+
return (0, storage_js_1.hasAuthTokens)();
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Post a single tweet
|
|
35
|
+
*/
|
|
36
|
+
async function postTweet(message) {
|
|
37
|
+
const client = getTwitterClient();
|
|
38
|
+
if (!client) {
|
|
39
|
+
throw new Error('Not authenticated. Please run mcp__bip__setup_auth first.');
|
|
40
|
+
}
|
|
41
|
+
console.error('📤 Posting tweet...');
|
|
42
|
+
try {
|
|
43
|
+
// Post tweet
|
|
44
|
+
const tweet = await client.v2.tweet(message);
|
|
45
|
+
// Get authenticated user info for URL
|
|
46
|
+
const me = await client.v2.me();
|
|
47
|
+
const username = me.data.username;
|
|
48
|
+
const tweetId = tweet.data.id;
|
|
49
|
+
const tweetUrl = `https://twitter.com/${username}/status/${tweetId}`;
|
|
50
|
+
// Save to history
|
|
51
|
+
(0, storage_js_1.addTweetToHistory)(tweetId, tweetUrl, message);
|
|
52
|
+
console.error('✅ Tweet posted successfully!');
|
|
53
|
+
console.error('🔗', tweetUrl);
|
|
54
|
+
return {
|
|
55
|
+
id: tweetId,
|
|
56
|
+
url: tweetUrl,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
console.error('❌ Failed to post tweet:', error);
|
|
61
|
+
console.error('Error details:', JSON.stringify(error, null, 2));
|
|
62
|
+
if (error.code === 403) {
|
|
63
|
+
throw new Error('Twitter API returned 403 Forbidden. This usually means:\n' +
|
|
64
|
+
'1. Your app does not have "Read and Write" permissions\n' +
|
|
65
|
+
'2. Your Access Tokens were generated BEFORE enabling write permissions\n' +
|
|
66
|
+
'3. You need to REGENERATE your Access Token & Secret after changing permissions\n\n' +
|
|
67
|
+
'Go to Twitter Developer Portal > Your App > Keys and tokens > Regenerate Access Token & Secret');
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Create a Twitter thread
|
|
74
|
+
* Posts multiple tweets in reply chain
|
|
75
|
+
*/
|
|
76
|
+
async function postThread(messages, replyToTweetId) {
|
|
77
|
+
const client = getTwitterClient();
|
|
78
|
+
if (!client) {
|
|
79
|
+
throw new Error('Not authenticated. Please run mcp__bip__setup_auth first.');
|
|
80
|
+
}
|
|
81
|
+
if (messages.length === 0) {
|
|
82
|
+
throw new Error('Thread must have at least one message');
|
|
83
|
+
}
|
|
84
|
+
console.error(`🧵 Creating thread with ${messages.length} tweets...`);
|
|
85
|
+
if (replyToTweetId) {
|
|
86
|
+
console.error(`📎 Replying to tweet: ${replyToTweetId}`);
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
// Get user info for URLs
|
|
90
|
+
const me = await client.v2.me();
|
|
91
|
+
const username = me.data.username;
|
|
92
|
+
const ids = [];
|
|
93
|
+
const urls = [];
|
|
94
|
+
// Start with replyToTweetId if provided
|
|
95
|
+
let previousTweetId = replyToTweetId;
|
|
96
|
+
// Post each tweet in the thread
|
|
97
|
+
for (let i = 0; i < messages.length; i++) {
|
|
98
|
+
const message = messages[i];
|
|
99
|
+
console.error(`📤 Posting tweet ${i + 1}/${messages.length}...`);
|
|
100
|
+
const tweet = await client.v2.tweet(message, {
|
|
101
|
+
reply: previousTweetId
|
|
102
|
+
? { in_reply_to_tweet_id: previousTweetId }
|
|
103
|
+
: undefined,
|
|
104
|
+
});
|
|
105
|
+
const tweetId = tweet.data.id;
|
|
106
|
+
const tweetUrl = `https://twitter.com/${username}/status/${tweetId}`;
|
|
107
|
+
ids.push(tweetId);
|
|
108
|
+
urls.push(tweetUrl);
|
|
109
|
+
previousTweetId = tweetId;
|
|
110
|
+
// Small delay between tweets to avoid rate limiting
|
|
111
|
+
if (i < messages.length - 1) {
|
|
112
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Save to history
|
|
116
|
+
(0, storage_js_1.addThreadToHistory)(ids[0], urls, messages);
|
|
117
|
+
console.error('✅ Thread posted successfully!');
|
|
118
|
+
console.error('🔗 Thread starts at:', urls[0]);
|
|
119
|
+
return {
|
|
120
|
+
ids,
|
|
121
|
+
urls,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
console.error('❌ Failed to post thread:', error);
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Verify API credentials work
|
|
131
|
+
*/
|
|
132
|
+
async function verifyCredentials() {
|
|
133
|
+
const client = getTwitterClient();
|
|
134
|
+
if (!client) {
|
|
135
|
+
throw new Error('Not authenticated');
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
const me = await client.v2.me();
|
|
139
|
+
return {
|
|
140
|
+
username: me.data.username,
|
|
141
|
+
name: me.data.name,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
console.error('❌ Failed to verify credentials:', error);
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=twitter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"twitter.js","sourceRoot":"","sources":["../../src/services/twitter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Twitter OAuth helpers
|
|
3
|
+
* PIN-based OAuth 1.0a flow
|
|
4
|
+
*/
|
|
5
|
+
import { TwitterApi } from 'twitter-api-v2';
|
|
6
|
+
import { AuthTokens } from '../services/storage.js';
|
|
7
|
+
/**
|
|
8
|
+
* Perform OAuth flow and save tokens
|
|
9
|
+
*/
|
|
10
|
+
export declare function performOAuthFlow(): Promise<AuthTokens>;
|
|
11
|
+
/**
|
|
12
|
+
* Create Twitter client from saved tokens
|
|
13
|
+
*/
|
|
14
|
+
export declare function createTwitterClient(tokens: AuthTokens): TwitterApi;
|
|
15
|
+
//# sourceMappingURL=oauth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../src/utils/oauth.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAI5C,OAAO,EAAkB,UAAU,EAAE,MAAM,wBAAwB,CAAC;AA2CpE;;GAEG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,UAAU,CAAC,CAmE5D;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,CAOlE"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Twitter OAuth helpers
|
|
4
|
+
* PIN-based OAuth 1.0a flow
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.performOAuthFlow = performOAuthFlow;
|
|
41
|
+
exports.createTwitterClient = createTwitterClient;
|
|
42
|
+
const twitter_api_v2_1 = require("twitter-api-v2");
|
|
43
|
+
const readline = __importStar(require("readline"));
|
|
44
|
+
const child_process_1 = require("child_process");
|
|
45
|
+
const util_1 = require("util");
|
|
46
|
+
const storage_js_1 = require("../services/storage.js");
|
|
47
|
+
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
48
|
+
/**
|
|
49
|
+
* Open URL in browser
|
|
50
|
+
*/
|
|
51
|
+
async function openBrowser(url) {
|
|
52
|
+
const platform = process.platform;
|
|
53
|
+
try {
|
|
54
|
+
if (platform === 'darwin') {
|
|
55
|
+
await execAsync(`open "${url}"`);
|
|
56
|
+
}
|
|
57
|
+
else if (platform === 'win32') {
|
|
58
|
+
await execAsync(`start "${url}"`);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
// Linux
|
|
62
|
+
await execAsync(`xdg-open "${url}"`);
|
|
63
|
+
}
|
|
64
|
+
console.error('🌐 Browser opened for authorization');
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
console.error('⚠️ Could not open browser automatically');
|
|
68
|
+
console.error('📎 Please open this URL manually:', url);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Get user input from stdin
|
|
73
|
+
*/
|
|
74
|
+
function getUserInput(prompt) {
|
|
75
|
+
const rl = readline.createInterface({
|
|
76
|
+
input: process.stdin,
|
|
77
|
+
output: process.stderr, // Use stderr to not interfere with STDIO protocol
|
|
78
|
+
});
|
|
79
|
+
return new Promise((resolve) => {
|
|
80
|
+
rl.question(prompt, (answer) => {
|
|
81
|
+
rl.close();
|
|
82
|
+
resolve(answer.trim());
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Perform OAuth flow and save tokens
|
|
88
|
+
*/
|
|
89
|
+
async function performOAuthFlow() {
|
|
90
|
+
console.error('🔐 Starting Twitter OAuth flow...\n');
|
|
91
|
+
// Step 1: Get API credentials from user
|
|
92
|
+
console.error('📋 You need API credentials from Twitter Developer Portal');
|
|
93
|
+
console.error(' https://developer.twitter.com/en/portal/dashboard\n');
|
|
94
|
+
const apiKey = await getUserInput('Enter your API Key (Consumer Key): ');
|
|
95
|
+
if (!apiKey) {
|
|
96
|
+
throw new Error('API Key is required');
|
|
97
|
+
}
|
|
98
|
+
const apiSecret = await getUserInput('Enter your API Secret (Consumer Secret): ');
|
|
99
|
+
if (!apiSecret) {
|
|
100
|
+
throw new Error('API Secret is required');
|
|
101
|
+
}
|
|
102
|
+
// Step 2: Initialize OAuth client
|
|
103
|
+
console.error('\n🔄 Initializing OAuth...');
|
|
104
|
+
const client = new twitter_api_v2_1.TwitterApi({
|
|
105
|
+
appKey: apiKey,
|
|
106
|
+
appSecret: apiSecret,
|
|
107
|
+
});
|
|
108
|
+
// Step 3: Get authorization URL
|
|
109
|
+
const authLink = await client.generateAuthLink(undefined, {
|
|
110
|
+
linkMode: 'authorize', // Use 'authorize' for PIN-based flow
|
|
111
|
+
});
|
|
112
|
+
console.error('\n✅ Authorization URL generated');
|
|
113
|
+
console.error('📱 Opening browser for authorization...\n');
|
|
114
|
+
// Step 4: Open browser
|
|
115
|
+
await openBrowser(authLink.url);
|
|
116
|
+
console.error('\n📝 After authorizing, Twitter will show you a PIN code');
|
|
117
|
+
// Step 5: Get PIN from user
|
|
118
|
+
const pin = await getUserInput('\nEnter the PIN code from Twitter: ');
|
|
119
|
+
if (!pin) {
|
|
120
|
+
throw new Error('PIN is required');
|
|
121
|
+
}
|
|
122
|
+
// Step 6: Exchange PIN for access tokens
|
|
123
|
+
console.error('\n🔄 Exchanging PIN for access tokens...');
|
|
124
|
+
const { client: loggedClient, accessToken, accessSecret } = await client.login(pin);
|
|
125
|
+
// Step 7: Verify credentials by getting user info
|
|
126
|
+
console.error('✅ Verifying credentials...');
|
|
127
|
+
const currentUser = await loggedClient.v2.me();
|
|
128
|
+
console.error(`\n🎉 Successfully authenticated as: @${currentUser.data.username}`);
|
|
129
|
+
// Step 8: Save tokens
|
|
130
|
+
const tokens = {
|
|
131
|
+
apiKey,
|
|
132
|
+
apiSecret,
|
|
133
|
+
accessToken,
|
|
134
|
+
accessTokenSecret: accessSecret,
|
|
135
|
+
};
|
|
136
|
+
(0, storage_js_1.saveAuthTokens)(tokens);
|
|
137
|
+
return tokens;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Create Twitter client from saved tokens
|
|
141
|
+
*/
|
|
142
|
+
function createTwitterClient(tokens) {
|
|
143
|
+
return new twitter_api_v2_1.TwitterApi({
|
|
144
|
+
appKey: tokens.apiKey,
|
|
145
|
+
appSecret: tokens.apiSecret,
|
|
146
|
+
accessToken: tokens.accessToken,
|
|
147
|
+
accessSecret: tokens.accessTokenSecret,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=oauth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../src/utils/oauth.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lucianfialho/build-in-public-mcp",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "MCP server for Build in Public - automatically share your dev progress on Twitter with AI-powered suggestions",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"build-in-public-mcp": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "tsc --watch",
|
|
12
|
+
"start": "node dist/index.js",
|
|
13
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"mcp",
|
|
17
|
+
"claude-code",
|
|
18
|
+
"twitter",
|
|
19
|
+
"build-in-public",
|
|
20
|
+
"automation",
|
|
21
|
+
"developer-tools"
|
|
22
|
+
],
|
|
23
|
+
"author": "Lucian Fialho",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18.0.0"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"server.json",
|
|
31
|
+
"README.md",
|
|
32
|
+
"LICENSE"
|
|
33
|
+
],
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
36
|
+
"twitter-api-v2": "^1.28.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^25.0.3",
|
|
40
|
+
"typescript": "^5.9.3"
|
|
41
|
+
}
|
|
42
|
+
}
|
package/server.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://modelcontextprotocol.io/schemas/server.json",
|
|
3
|
+
"name": "build-in-public",
|
|
4
|
+
"description": "MCP server for Build in Public - automatically share your dev progress on Twitter",
|
|
5
|
+
"version": "0.2.0",
|
|
6
|
+
"transport": "stdio",
|
|
7
|
+
"environmentVariables": [
|
|
8
|
+
{
|
|
9
|
+
"name": "TWITTER_API_KEY",
|
|
10
|
+
"description": "Twitter API Key (Consumer Key) - Optional if using interactive OAuth",
|
|
11
|
+
"format": "string",
|
|
12
|
+
"isRequired": false,
|
|
13
|
+
"isSecret": true
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"name": "TWITTER_API_SECRET",
|
|
17
|
+
"description": "Twitter API Secret (Consumer Secret) - Optional if using interactive OAuth",
|
|
18
|
+
"format": "string",
|
|
19
|
+
"isRequired": false,
|
|
20
|
+
"isSecret": true
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "TWITTER_ACCESS_TOKEN",
|
|
24
|
+
"description": "Twitter Access Token - Optional if using interactive OAuth",
|
|
25
|
+
"format": "string",
|
|
26
|
+
"isRequired": false,
|
|
27
|
+
"isSecret": true
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "TWITTER_ACCESS_SECRET",
|
|
31
|
+
"description": "Twitter Access Token Secret - Optional if using interactive OAuth",
|
|
32
|
+
"format": "string",
|
|
33
|
+
"isRequired": false,
|
|
34
|
+
"isSecret": true
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|