slashvibe-mcp 0.3.20 โ 0.3.21
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/README.md +47 -252
- package/analytics.js +107 -0
- package/auth-store.js +148 -0
- package/auto-update.js +130 -0
- package/bridges/bridge-monitor.js +388 -0
- package/bridges/discord-bot.js +431 -0
- package/bridges/farcaster.js +299 -0
- package/bridges/telegram.js +261 -0
- package/bridges/webhook-health.js +420 -0
- package/bridges/webhook-server.js +437 -0
- package/bridges/whatsapp.js +441 -0
- package/bridges/x-webhook.js +423 -0
- package/config.js +27 -15
- package/games/arcade.js +406 -0
- package/games/chess.js +451 -0
- package/games/colorguess.js +343 -0
- package/games/crossword-words.js +171 -0
- package/games/crossword.js +461 -0
- package/games/drawing.js +347 -0
- package/games/gameroulette.js +300 -0
- package/games/gamerouter.js +336 -0
- package/games/gamestatus.js +337 -0
- package/games/guessnumber.js +209 -0
- package/games/hangman.js +279 -0
- package/games/memory.js +338 -0
- package/games/multiplayer-tictactoe.js +389 -0
- package/games/pixelart.js +399 -0
- package/games/quickduel.js +354 -0
- package/games/riddle.js +371 -0
- package/games/rockpaperscissors.js +291 -0
- package/games/snake.js +406 -0
- package/games/storybuilder.js +343 -0
- package/games/tictactoe.js +345 -0
- package/games/twentyquestions.js +286 -0
- package/games/twotruths.js +207 -0
- package/games/werewolf.js +508 -0
- package/games/wordassociation.js +247 -0
- package/games/wordchain.js +135 -0
- package/index.js +116 -159
- package/intelligence/index.js +9 -2
- package/intelligence/interests.js +369 -0
- package/notification-emitter.js +77 -0
- package/notify.js +5 -1
- package/package.json +21 -16
- package/prompts.js +1 -1
- package/protocol/index.js +73 -0
- package/setup.js +480 -0
- package/smart-inbox.js +276 -0
- package/store/api.js +536 -215
- package/store/profiles.js +160 -12
- package/tools/_actions.js +362 -21
- package/tools/_discovery.js +119 -26
- package/tools/_shared/index.js +64 -0
- package/tools/_shared.js +234 -0
- package/tools/_work-context.js +338 -0
- package/tools/_work-context.manual-test.js +199 -0
- package/tools/_work-context.test.js +260 -0
- package/tools/activity.js +220 -0
- package/tools/analytics.js +191 -0
- package/tools/approve.js +197 -0
- package/tools/artifact-create.js +14 -3
- package/tools/artifacts-price.js +107 -0
- package/tools/available.js +120 -0
- package/tools/broadcast.js +325 -0
- package/tools/chat.js +202 -0
- package/tools/collaborative-drawing.js +1 -1
- package/tools/connection-status.js +178 -0
- package/tools/discover.js +350 -34
- package/tools/dm.js +80 -8
- package/tools/earnings.js +126 -0
- package/tools/feed.js +35 -4
- package/tools/follow.js +224 -0
- package/tools/friends.js +207 -0
- package/tools/gig-browse.js +206 -0
- package/tools/gig-complete.js +144 -0
- package/tools/health.js +87 -0
- package/tools/help.js +3 -3
- package/tools/idea.js +9 -2
- package/tools/inbox.js +289 -105
- package/tools/init.js +131 -34
- package/tools/invite.js +15 -4
- package/tools/leaderboard.js +117 -0
- package/tools/lib/git-apply.js +206 -0
- package/tools/lib/git-bundle.js +407 -0
- package/tools/migrate.js +3 -3
- package/tools/multiplayer-game.js +1 -1
- package/tools/onboarding.js +7 -7
- package/tools/open.js +143 -12
- package/tools/party-game.js +1 -1
- package/tools/plan.js +225 -0
- package/tools/proof-of-work.js +144 -0
- package/tools/reply.js +166 -0
- package/tools/report.js +1 -1
- package/tools/request.js +17 -3
- package/tools/schedule.js +367 -0
- package/tools/search-messages.js +123 -0
- package/tools/session.js +467 -0
- package/tools/session_price.js +128 -0
- package/tools/settings.js +90 -2
- package/tools/ship.js +30 -7
- package/tools/smart-check.js +201 -0
- package/tools/start.js +147 -12
- package/tools/status.js +53 -6
- package/tools/streak.js +147 -0
- package/tools/stuck.js +297 -0
- package/tools/subscribe.js +148 -0
- package/tools/subscriptions.js +134 -0
- package/tools/suggest-tags.js +6 -8
- package/tools/tag-suggestions.js +1 -1
- package/tools/tip.js +150 -77
- package/tools/token.js +4 -4
- package/tools/update.js +1 -1
- package/tools/wallet.js +221 -79
- package/tools/watch.js +157 -0
- package/tools/who.js +30 -1
- package/tools/withdraw.js +145 -0
- package/tools/work-summary.js +96 -0
- package/version.json +10 -8
- package/LICENSE +0 -21
- package/store/sqlite.js +0 -347
- /package/tools/{auto-suggest-connections.js โ _deprecated/auto-suggest-connections.js} +0 -0
- /package/tools/{away.js โ _deprecated/away.js} +0 -0
- /package/tools/{back.js โ _deprecated/back.js} +0 -0
- /package/tools/{bootstrap-skills.js โ _deprecated/bootstrap-skills.js} +0 -0
- /package/tools/{bridge-dashboard.js โ _deprecated/bridge-dashboard.js} +0 -0
- /package/tools/{bridge-health.js โ _deprecated/bridge-health.js} +0 -0
- /package/tools/{bridge-live.js โ _deprecated/bridge-live.js} +0 -0
- /package/tools/{bridges.js โ _deprecated/bridges.js} +0 -0
- /package/tools/{colorguess.js โ _deprecated/colorguess.js} +0 -0
- /package/tools/{discover-insights.js โ _deprecated/discover-insights.js} +0 -0
- /package/tools/{discover-momentum.js โ _deprecated/discover-momentum.js} +0 -0
- /package/tools/{discovery-analytics.js โ _deprecated/discovery-analytics.js} +0 -0
- /package/tools/{discovery-auto-suggest.js โ _deprecated/discovery-auto-suggest.js} +0 -0
- /package/tools/{discovery-bootstrap.js โ _deprecated/discovery-bootstrap.js} +0 -0
- /package/tools/{discovery-daily.js โ _deprecated/discovery-daily.js} +0 -0
- /package/tools/{discovery-dashboard.js โ _deprecated/discovery-dashboard.js} +0 -0
- /package/tools/{discovery-digest.js โ _deprecated/discovery-digest.js} +0 -0
- /package/tools/{discovery-hub.js โ _deprecated/discovery-hub.js} +0 -0
- /package/tools/{discovery-insights.js โ _deprecated/discovery-insights.js} +0 -0
- /package/tools/{discovery-momentum.js โ _deprecated/discovery-momentum.js} +0 -0
- /package/tools/{discovery-monitor.js โ _deprecated/discovery-monitor.js} +0 -0
- /package/tools/{discovery-proactive.js โ _deprecated/discovery-proactive.js} +0 -0
- /package/tools/{draw.js โ _deprecated/draw.js} +0 -0
- /package/tools/{farcaster.js โ _deprecated/farcaster.js} +0 -0
- /package/tools/{forget.js โ _deprecated/forget.js} +0 -0
- /package/tools/{games-catalog.js โ _deprecated/games-catalog.js} +0 -0
- /package/tools/{games.js โ _deprecated/games.js} +0 -0
- /package/tools/{guessnumber.js โ _deprecated/guessnumber.js} +0 -0
- /package/tools/{hangman.js โ _deprecated/hangman.js} +0 -0
- /package/tools/{multiplayer-tictactoe.js โ _deprecated/multiplayer-tictactoe.js} +0 -0
- /package/tools/{mute.js โ _deprecated/mute.js} +0 -0
- /package/tools/{recall.js โ _deprecated/recall.js} +0 -0
- /package/tools/{remember.js โ _deprecated/remember.js} +0 -0
- /package/tools/{riddle.js โ _deprecated/riddle.js} +0 -0
- /package/tools/{run-bootstrap.js โ _deprecated/run-bootstrap.js} +0 -0
- /package/tools/{skills-analytics.js โ _deprecated/skills-analytics.js} +0 -0
- /package/tools/{skills-bootstrap.js โ _deprecated/skills-bootstrap.js} +0 -0
- /package/tools/{skills-dashboard.js โ _deprecated/skills-dashboard.js} +0 -0
- /package/tools/{skills-exchange.js โ _deprecated/skills-exchange.js} +0 -0
- /package/tools/{skills.js โ _deprecated/skills.js} +0 -0
- /package/tools/{smart-intro.js โ _deprecated/smart-intro.js} +0 -0
- /package/tools/{storybuilder.js โ _deprecated/storybuilder.js} +0 -0
- /package/tools/{telegram-bot.js โ _deprecated/telegram-bot.js} +0 -0
- /package/tools/{telegram-setup.js โ _deprecated/telegram-setup.js} +0 -0
- /package/tools/{tictactoe.js โ _deprecated/tictactoe.js} +0 -0
- /package/tools/{twentyquestions.js โ _deprecated/twentyquestions.js} +0 -0
- /package/tools/{wordassociation.js โ _deprecated/wordassociation.js} +0 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git Bundle Library
|
|
3
|
+
*
|
|
4
|
+
* Creates and validates git bundles for session sharing.
|
|
5
|
+
* Used by broadcast.js to capture code changes during a broadcast.
|
|
6
|
+
*
|
|
7
|
+
* Key functions:
|
|
8
|
+
* - captureSessionStart() - Returns current HEAD commit
|
|
9
|
+
* - createBundle(startCommit) - Creates bundle from startCommit to HEAD
|
|
10
|
+
* - validateBundle(buffer) - Validates bundle integrity
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { execSync, spawn } = require('child_process');
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const os = require('os');
|
|
17
|
+
|
|
18
|
+
// Max bundle size (10MB)
|
|
19
|
+
const MAX_BUNDLE_SIZE = 10 * 1024 * 1024;
|
|
20
|
+
|
|
21
|
+
// Files to exclude from bundles (security)
|
|
22
|
+
const EXCLUDED_PATTERNS = [
|
|
23
|
+
'.env',
|
|
24
|
+
'.env.*',
|
|
25
|
+
'*.pem',
|
|
26
|
+
'*.key',
|
|
27
|
+
'*.p12',
|
|
28
|
+
'*.pfx',
|
|
29
|
+
'.npmrc',
|
|
30
|
+
'.netrc',
|
|
31
|
+
'credentials.json',
|
|
32
|
+
'secrets.json',
|
|
33
|
+
'*_rsa',
|
|
34
|
+
'*_dsa',
|
|
35
|
+
'*_ed25519',
|
|
36
|
+
'*_ecdsa',
|
|
37
|
+
'id_rsa*',
|
|
38
|
+
'id_dsa*',
|
|
39
|
+
'*.keystore',
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check if we're in a git repository
|
|
44
|
+
*/
|
|
45
|
+
function isGitRepo() {
|
|
46
|
+
try {
|
|
47
|
+
execSync('git rev-parse --is-inside-work-tree', {
|
|
48
|
+
stdio: 'pipe',
|
|
49
|
+
encoding: 'utf8',
|
|
50
|
+
});
|
|
51
|
+
return true;
|
|
52
|
+
} catch (e) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get current git branch
|
|
59
|
+
*/
|
|
60
|
+
function getCurrentBranch() {
|
|
61
|
+
try {
|
|
62
|
+
return execSync('git rev-parse --abbrev-ref HEAD', {
|
|
63
|
+
stdio: 'pipe',
|
|
64
|
+
encoding: 'utf8',
|
|
65
|
+
}).trim();
|
|
66
|
+
} catch (e) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get current HEAD commit hash
|
|
73
|
+
*/
|
|
74
|
+
function getHeadCommit() {
|
|
75
|
+
try {
|
|
76
|
+
return execSync('git rev-parse HEAD', {
|
|
77
|
+
stdio: 'pipe',
|
|
78
|
+
encoding: 'utf8',
|
|
79
|
+
}).trim();
|
|
80
|
+
} catch (e) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get commit count between two commits
|
|
87
|
+
*/
|
|
88
|
+
function getCommitCount(startCommit, endCommit = 'HEAD') {
|
|
89
|
+
try {
|
|
90
|
+
const output = execSync(`git rev-list --count ${startCommit}..${endCommit}`, {
|
|
91
|
+
stdio: 'pipe',
|
|
92
|
+
encoding: 'utf8',
|
|
93
|
+
});
|
|
94
|
+
return parseInt(output.trim(), 10);
|
|
95
|
+
} catch (e) {
|
|
96
|
+
return 0;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get list of files changed between commits
|
|
102
|
+
*/
|
|
103
|
+
function getChangedFiles(startCommit, endCommit = 'HEAD') {
|
|
104
|
+
try {
|
|
105
|
+
const output = execSync(`git diff --name-only ${startCommit}..${endCommit}`, {
|
|
106
|
+
stdio: 'pipe',
|
|
107
|
+
encoding: 'utf8',
|
|
108
|
+
});
|
|
109
|
+
return output.trim().split('\n').filter(Boolean);
|
|
110
|
+
} catch (e) {
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Check if any excluded files would be in the bundle
|
|
117
|
+
*/
|
|
118
|
+
function hasExcludedFiles(changedFiles) {
|
|
119
|
+
const excluded = [];
|
|
120
|
+
|
|
121
|
+
for (const file of changedFiles) {
|
|
122
|
+
const basename = path.basename(file);
|
|
123
|
+
|
|
124
|
+
for (const pattern of EXCLUDED_PATTERNS) {
|
|
125
|
+
// Simple glob matching
|
|
126
|
+
if (pattern.startsWith('*')) {
|
|
127
|
+
const suffix = pattern.slice(1);
|
|
128
|
+
if (basename.endsWith(suffix)) {
|
|
129
|
+
excluded.push(file);
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
} else if (pattern.endsWith('*')) {
|
|
133
|
+
const prefix = pattern.slice(0, -1);
|
|
134
|
+
if (basename.startsWith(prefix)) {
|
|
135
|
+
excluded.push(file);
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
} else if (pattern.includes('*')) {
|
|
139
|
+
// Pattern like ".env.*"
|
|
140
|
+
const [prefix, suffix] = pattern.split('*');
|
|
141
|
+
if (basename.startsWith(prefix) && (suffix === '' || basename.endsWith(suffix))) {
|
|
142
|
+
excluded.push(file);
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
} else if (basename === pattern || file === pattern) {
|
|
146
|
+
excluded.push(file);
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return excluded;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Capture git state at session start
|
|
157
|
+
* @returns {object} - { success, commit, branch, error? }
|
|
158
|
+
*/
|
|
159
|
+
function captureSessionStart() {
|
|
160
|
+
if (!isGitRepo()) {
|
|
161
|
+
return { success: false, error: 'Not a git repository' };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const commit = getHeadCommit();
|
|
165
|
+
const branch = getCurrentBranch();
|
|
166
|
+
|
|
167
|
+
if (!commit) {
|
|
168
|
+
return { success: false, error: 'Could not get HEAD commit' };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
success: true,
|
|
173
|
+
commit,
|
|
174
|
+
branch,
|
|
175
|
+
capturedAt: new Date().toISOString(),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Create a git bundle from startCommit to HEAD
|
|
181
|
+
* @param {string} startCommit - Starting commit hash
|
|
182
|
+
* @returns {object} - { success, buffer, metadata, error? }
|
|
183
|
+
*/
|
|
184
|
+
function createBundle(startCommit) {
|
|
185
|
+
if (!isGitRepo()) {
|
|
186
|
+
return { success: false, error: 'Not a git repository' };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (!startCommit) {
|
|
190
|
+
return { success: false, error: 'Start commit required' };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const endCommit = getHeadCommit();
|
|
194
|
+
const branch = getCurrentBranch();
|
|
195
|
+
|
|
196
|
+
if (!endCommit) {
|
|
197
|
+
return { success: false, error: 'Could not get HEAD commit' };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Check if there are any new commits
|
|
201
|
+
const commitCount = getCommitCount(startCommit, endCommit);
|
|
202
|
+
if (commitCount === 0) {
|
|
203
|
+
return {
|
|
204
|
+
success: false,
|
|
205
|
+
error: 'No new commits since session start',
|
|
206
|
+
metadata: {
|
|
207
|
+
startCommit,
|
|
208
|
+
endCommit,
|
|
209
|
+
branch,
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Check for excluded files
|
|
215
|
+
const changedFiles = getChangedFiles(startCommit, endCommit);
|
|
216
|
+
const excludedFiles = hasExcludedFiles(changedFiles);
|
|
217
|
+
|
|
218
|
+
if (excludedFiles.length > 0) {
|
|
219
|
+
console.log(`[git-bundle] Warning: Excluding sensitive files: ${excludedFiles.join(', ')}`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Create temp file for bundle
|
|
223
|
+
const tmpDir = os.tmpdir();
|
|
224
|
+
const bundlePath = path.join(tmpDir, `vibe-bundle-${Date.now()}.bundle`);
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
// Create the bundle
|
|
228
|
+
// Format: git bundle create <file> <range>
|
|
229
|
+
const bundleRange = `${startCommit}..HEAD`;
|
|
230
|
+
|
|
231
|
+
execSync(`git bundle create "${bundlePath}" ${bundleRange}`, {
|
|
232
|
+
stdio: 'pipe',
|
|
233
|
+
encoding: 'utf8',
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Read the bundle
|
|
237
|
+
const buffer = fs.readFileSync(bundlePath);
|
|
238
|
+
|
|
239
|
+
// Check size
|
|
240
|
+
if (buffer.length > MAX_BUNDLE_SIZE) {
|
|
241
|
+
fs.unlinkSync(bundlePath);
|
|
242
|
+
return {
|
|
243
|
+
success: false,
|
|
244
|
+
error: `Bundle too large (${Math.round(buffer.length / 1024 / 1024)}MB > ${MAX_BUNDLE_SIZE / 1024 / 1024}MB limit)`,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Clean up temp file
|
|
249
|
+
fs.unlinkSync(bundlePath);
|
|
250
|
+
|
|
251
|
+
const metadata = {
|
|
252
|
+
initialCommit: startCommit,
|
|
253
|
+
finalCommit: endCommit,
|
|
254
|
+
branch,
|
|
255
|
+
commitCount,
|
|
256
|
+
changedFiles: changedFiles.filter((f) => !excludedFiles.includes(f)),
|
|
257
|
+
excludedFiles,
|
|
258
|
+
size: buffer.length,
|
|
259
|
+
createdAt: new Date().toISOString(),
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
console.log(`[git-bundle] Created bundle: ${commitCount} commits, ${changedFiles.length} files, ${buffer.length} bytes`);
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
success: true,
|
|
266
|
+
buffer,
|
|
267
|
+
metadata,
|
|
268
|
+
};
|
|
269
|
+
} catch (error) {
|
|
270
|
+
// Clean up on error
|
|
271
|
+
try {
|
|
272
|
+
if (fs.existsSync(bundlePath)) {
|
|
273
|
+
fs.unlinkSync(bundlePath);
|
|
274
|
+
}
|
|
275
|
+
} catch (e) {
|
|
276
|
+
// Ignore cleanup errors
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
console.error('[git-bundle] Failed to create bundle:', error.message);
|
|
280
|
+
return { success: false, error: error.message };
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Validate a git bundle
|
|
286
|
+
* @param {Buffer} buffer - The bundle data
|
|
287
|
+
* @returns {object} - { success, info, error? }
|
|
288
|
+
*/
|
|
289
|
+
function validateBundle(buffer) {
|
|
290
|
+
if (!buffer || buffer.length === 0) {
|
|
291
|
+
return { success: false, error: 'Empty buffer' };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Check magic bytes (git bundles start with "# v2 git bundle" or "# v3 git bundle")
|
|
295
|
+
const header = buffer.slice(0, 20).toString('utf8');
|
|
296
|
+
if (!header.startsWith('# v2 git bundle') && !header.startsWith('# v3 git bundle')) {
|
|
297
|
+
return { success: false, error: 'Invalid bundle format' };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Write to temp and verify
|
|
301
|
+
const tmpDir = os.tmpdir();
|
|
302
|
+
const bundlePath = path.join(tmpDir, `vibe-verify-${Date.now()}.bundle`);
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
fs.writeFileSync(bundlePath, buffer);
|
|
306
|
+
|
|
307
|
+
// Verify the bundle
|
|
308
|
+
const output = execSync(`git bundle verify "${bundlePath}"`, {
|
|
309
|
+
stdio: 'pipe',
|
|
310
|
+
encoding: 'utf8',
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
fs.unlinkSync(bundlePath);
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
success: true,
|
|
317
|
+
info: output.trim(),
|
|
318
|
+
size: buffer.length,
|
|
319
|
+
};
|
|
320
|
+
} catch (error) {
|
|
321
|
+
try {
|
|
322
|
+
if (fs.existsSync(bundlePath)) {
|
|
323
|
+
fs.unlinkSync(bundlePath);
|
|
324
|
+
}
|
|
325
|
+
} catch (e) {
|
|
326
|
+
// Ignore cleanup errors
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return { success: false, error: error.message };
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Apply a bundle to create a new branch (for forking)
|
|
335
|
+
* @param {Buffer} buffer - The bundle data
|
|
336
|
+
* @param {string} branchName - Name for the new branch
|
|
337
|
+
* @returns {object} - { success, branch, error? }
|
|
338
|
+
*/
|
|
339
|
+
function applyBundle(buffer, branchName = 'forked-session') {
|
|
340
|
+
if (!isGitRepo()) {
|
|
341
|
+
return { success: false, error: 'Not a git repository' };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const validation = validateBundle(buffer);
|
|
345
|
+
if (!validation.success) {
|
|
346
|
+
return { success: false, error: `Invalid bundle: ${validation.error}` };
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const tmpDir = os.tmpdir();
|
|
350
|
+
const bundlePath = path.join(tmpDir, `vibe-apply-${Date.now()}.bundle`);
|
|
351
|
+
|
|
352
|
+
try {
|
|
353
|
+
fs.writeFileSync(bundlePath, buffer);
|
|
354
|
+
|
|
355
|
+
// Get the refs in the bundle
|
|
356
|
+
const listOutput = execSync(`git bundle list-heads "${bundlePath}"`, {
|
|
357
|
+
stdio: 'pipe',
|
|
358
|
+
encoding: 'utf8',
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
const refs = listOutput.trim().split('\n');
|
|
362
|
+
if (refs.length === 0) {
|
|
363
|
+
throw new Error('No refs in bundle');
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Get the first commit hash from the bundle
|
|
367
|
+
const firstRef = refs[0].split(/\s+/)[0];
|
|
368
|
+
|
|
369
|
+
// Unbundle (fetch from bundle)
|
|
370
|
+
execSync(`git fetch "${bundlePath}" ${firstRef}:${branchName}`, {
|
|
371
|
+
stdio: 'pipe',
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
fs.unlinkSync(bundlePath);
|
|
375
|
+
|
|
376
|
+
console.log(`[git-bundle] Applied bundle to branch: ${branchName}`);
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
success: true,
|
|
380
|
+
branch: branchName,
|
|
381
|
+
refs: refs.map((r) => r.split(/\s+/)),
|
|
382
|
+
};
|
|
383
|
+
} catch (error) {
|
|
384
|
+
try {
|
|
385
|
+
if (fs.existsSync(bundlePath)) {
|
|
386
|
+
fs.unlinkSync(bundlePath);
|
|
387
|
+
}
|
|
388
|
+
} catch (e) {
|
|
389
|
+
// Ignore cleanup errors
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
console.error('[git-bundle] Failed to apply bundle:', error.message);
|
|
393
|
+
return { success: false, error: error.message };
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
module.exports = {
|
|
398
|
+
isGitRepo,
|
|
399
|
+
getCurrentBranch,
|
|
400
|
+
getHeadCommit,
|
|
401
|
+
captureSessionStart,
|
|
402
|
+
createBundle,
|
|
403
|
+
validateBundle,
|
|
404
|
+
applyBundle,
|
|
405
|
+
MAX_BUNDLE_SIZE,
|
|
406
|
+
EXCLUDED_PATTERNS,
|
|
407
|
+
};
|
package/tools/migrate.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* vibe migrate โ Migrate existing handle to GitHub auth
|
|
3
3
|
*
|
|
4
4
|
* For users who have existing handles with local keypairs,
|
|
5
|
-
* this command helps them migrate to
|
|
5
|
+
* this command helps them migrate to GitHub OAuth auth.
|
|
6
6
|
*
|
|
7
7
|
* Flow:
|
|
8
8
|
* 1. Check if user has existing handle and keys
|
|
@@ -36,8 +36,8 @@ If you're new, use \`vibe init @yourhandle "what you're building"\` instead.`
|
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
// Check if already using
|
|
40
|
-
if (config.
|
|
39
|
+
// Check if already using GitHub auth
|
|
40
|
+
if (config.hasOAuth()) {
|
|
41
41
|
return {
|
|
42
42
|
display: `โ
**Already using GitHub auth**
|
|
43
43
|
|
package/tools/onboarding.js
CHANGED
|
@@ -110,23 +110,23 @@ Error: ${response.error || 'Unknown error'}`
|
|
|
110
110
|
if (nextTask) {
|
|
111
111
|
switch (nextTask.id) {
|
|
112
112
|
case 'read_welcome':
|
|
113
|
-
display += 'โ Say **"check my messages"** to read @
|
|
113
|
+
display += 'โ Say **"check my messages"** to read @seth\'s welcome\n';
|
|
114
114
|
break;
|
|
115
115
|
case 'reply_seth':
|
|
116
|
-
display += 'โ Say **"reply to
|
|
116
|
+
display += 'โ Say **"reply to seth"** or **"dm @seth hi!"**\n';
|
|
117
117
|
break;
|
|
118
|
-
case '
|
|
119
|
-
display += 'โ Say **"
|
|
118
|
+
case 'find_github_friends':
|
|
119
|
+
display += 'โ Say **"find my github friends"** to see who you know! ๐ฅ\n';
|
|
120
120
|
break;
|
|
121
121
|
case 'first_ship':
|
|
122
122
|
display += 'โ Say **"ship something"** or **"I shipped X"**\n';
|
|
123
123
|
break;
|
|
124
|
+
case 'share_ship':
|
|
125
|
+
display += 'โ After shipping, click the Twitter link to share! ๐ฆ\n';
|
|
126
|
+
break;
|
|
124
127
|
case 'invite_friend':
|
|
125
128
|
display += 'โ Say **"invite a friend"** or **"share my invite link"**\n';
|
|
126
129
|
break;
|
|
127
|
-
case 'leave_feedback':
|
|
128
|
-
display += 'โ Say **"echo feedback about X"** or **"give feedback"**\n';
|
|
129
|
-
break;
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
}
|
package/tools/open.js
CHANGED
|
@@ -8,6 +8,15 @@ const memory = require('../memory');
|
|
|
8
8
|
const patterns = require('../intelligence/patterns');
|
|
9
9
|
const { formatPayload } = require('../protocol');
|
|
10
10
|
const { requireInit, normalizeHandle } = require('./_shared');
|
|
11
|
+
const { actions, formatActions } = require('./_actions');
|
|
12
|
+
|
|
13
|
+
// Truncate message for preview (first 100 chars, clean break at word)
|
|
14
|
+
function summarizeMessage(text, maxLen = 100) {
|
|
15
|
+
if (!text || text.length <= maxLen) return text;
|
|
16
|
+
const truncated = text.slice(0, maxLen);
|
|
17
|
+
const lastSpace = truncated.lastIndexOf(' ');
|
|
18
|
+
return (lastSpace > maxLen * 0.7 ? truncated.slice(0, lastSpace) : truncated) + '...';
|
|
19
|
+
}
|
|
11
20
|
|
|
12
21
|
const definition = {
|
|
13
22
|
name: 'vibe_open',
|
|
@@ -46,38 +55,152 @@ async function handler(args) {
|
|
|
46
55
|
patterns.logMessageReceived(them);
|
|
47
56
|
}
|
|
48
57
|
|
|
49
|
-
// Check if they're typing
|
|
58
|
+
// Check if they're typing (cross-platform: synced via KV)
|
|
50
59
|
let typingNotice = '';
|
|
51
60
|
try {
|
|
52
61
|
const typingUsers = await store.getTypingUsers(myHandle);
|
|
53
|
-
|
|
62
|
+
const isTyping = typingUsers.some(t => t.handle?.toLowerCase() === them.toLowerCase());
|
|
63
|
+
if (isTyping) {
|
|
54
64
|
typingNotice = `\n_@${them} is typing..._\n`;
|
|
55
65
|
}
|
|
56
66
|
} catch (e) {}
|
|
57
67
|
|
|
68
|
+
// Get their presence status (for "last seen" display)
|
|
69
|
+
let presenceStatus = '';
|
|
70
|
+
try {
|
|
71
|
+
const activeUsers = await store.getActiveUsers();
|
|
72
|
+
const theirPresence = activeUsers.find(u => u.handle?.toLowerCase() === them.toLowerCase());
|
|
73
|
+
|
|
74
|
+
if (theirPresence) {
|
|
75
|
+
if (theirPresence.status === 'active') {
|
|
76
|
+
presenceStatus = '๐ข online now';
|
|
77
|
+
} else if (theirPresence.status === 'away') {
|
|
78
|
+
const ago = store.formatTimeAgo(theirPresence.lastSeen);
|
|
79
|
+
presenceStatus = `โ away ยท last seen ${ago}`;
|
|
80
|
+
} else {
|
|
81
|
+
const ago = store.formatTimeAgo(theirPresence.lastSeen);
|
|
82
|
+
presenceStatus = `last seen ${ago}`;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch (e) {}
|
|
86
|
+
|
|
58
87
|
if (thread.length === 0) {
|
|
59
|
-
|
|
60
|
-
|
|
88
|
+
const statusLine = presenceStatus ? `\n_${presenceStatus}_` : '';
|
|
89
|
+
|
|
90
|
+
// Fetch conversation starters to help break the ice
|
|
91
|
+
let starters = [];
|
|
92
|
+
let starterContext = {};
|
|
93
|
+
try {
|
|
94
|
+
const apiUrl = config.getApiUrl();
|
|
95
|
+
const promptsResponse = await fetch(`${apiUrl}/api/prompts?from=${myHandle}&to=${them}`, {
|
|
96
|
+
headers: { 'User-Agent': 'vibe-mcp-client' }
|
|
97
|
+
});
|
|
98
|
+
if (promptsResponse.ok) {
|
|
99
|
+
const data = await promptsResponse.json();
|
|
100
|
+
if (data.success && data.prompts) {
|
|
101
|
+
starters = data.prompts.slice(0, 3);
|
|
102
|
+
starterContext = data.context || {};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} catch (e) {}
|
|
106
|
+
|
|
107
|
+
// Build display with conversation starters
|
|
108
|
+
let emptyDisplay = `## @${them}${statusLine}
|
|
61
109
|
|
|
62
110
|
_No messages yet._${typingNotice}
|
|
111
|
+
`;
|
|
112
|
+
|
|
113
|
+
if (starters.length > 0) {
|
|
114
|
+
emptyDisplay += `\n**๐ฌ Conversation starters:**\n`;
|
|
115
|
+
starters.forEach((starter, i) => {
|
|
116
|
+
emptyDisplay += `${i + 1}. "${starter}"\n`;
|
|
117
|
+
});
|
|
118
|
+
emptyDisplay += `\n_Copy one or say your own!_`;
|
|
119
|
+
} else {
|
|
120
|
+
emptyDisplay += `\nSay "message ${them} hello" to start`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Build response with actions for quick-send
|
|
124
|
+
const response = { display: emptyDisplay };
|
|
125
|
+
|
|
126
|
+
if (starters.length > 0) {
|
|
127
|
+
response.actions = formatActions(starters.map((starter, i) => ({
|
|
128
|
+
label: `Send #${i + 1}`,
|
|
129
|
+
action: `vibe dm @${them} ${starter}`,
|
|
130
|
+
description: starter.slice(0, 40) + (starter.length > 40 ? '...' : '')
|
|
131
|
+
})));
|
|
132
|
+
response.conversationStarters = starters;
|
|
133
|
+
response.context = starterContext;
|
|
134
|
+
}
|
|
63
135
|
|
|
64
|
-
|
|
65
|
-
};
|
|
136
|
+
return response;
|
|
66
137
|
}
|
|
67
138
|
|
|
68
|
-
//
|
|
69
|
-
const
|
|
70
|
-
|
|
139
|
+
// Get latest message from them for the summary preview
|
|
140
|
+
const latestFromThem = theirMessages.length > 0
|
|
141
|
+
? theirMessages.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0))[0]
|
|
142
|
+
: null;
|
|
143
|
+
|
|
144
|
+
// Build the preview summary line (what appears in collapsed view)
|
|
145
|
+
let display = '';
|
|
146
|
+
|
|
147
|
+
// Header with presence status
|
|
148
|
+
const statusSuffix = presenceStatus ? ` ยท _${presenceStatus}_` : '';
|
|
71
149
|
|
|
72
|
-
|
|
150
|
+
if (latestFromThem) {
|
|
151
|
+
const agentBadge = latestFromThem.isAgent ? ' ๐ค' : '';
|
|
152
|
+
const time = store.formatTimeAgo(latestFromThem.timestamp);
|
|
153
|
+
const preview = latestFromThem.body
|
|
154
|
+
? summarizeMessage(latestFromThem.body)
|
|
155
|
+
: (latestFromThem.payload ? '[attachment]' : '');
|
|
73
156
|
|
|
74
|
-
|
|
157
|
+
display = `๐ฌ @${them}${agentBadge}${statusSuffix}\n> (${time}): "${preview}"\n\n`;
|
|
158
|
+
} else {
|
|
159
|
+
// No messages from them yet - you sent first
|
|
160
|
+
display = `๐ฌ @${them}${statusSuffix}\n> _Waiting for reply..._\n\n`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Thread section - sorted newest first so their latest message appears at top
|
|
164
|
+
display += `---\n๐ Thread\n\n`;
|
|
165
|
+
|
|
166
|
+
const sortedThread = [...thread].sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
|
|
167
|
+
|
|
168
|
+
// Build a map for reply lookups
|
|
169
|
+
const messageMap = {};
|
|
170
|
+
thread.forEach(m => { messageMap[m.id] = m; });
|
|
171
|
+
|
|
172
|
+
sortedThread.forEach(m => {
|
|
75
173
|
const isMe = m.from === myHandle;
|
|
76
174
|
const agentBadge = m.isAgent && !isMe ? '๐ค ' : '';
|
|
77
175
|
const sender = isMe ? 'you' : `@${m.from}`;
|
|
78
176
|
const time = store.formatTimeAgo(m.timestamp);
|
|
79
177
|
|
|
80
|
-
|
|
178
|
+
// Status indicator for sent messages: โ sent, โโ delivered, โโ read
|
|
179
|
+
let statusIndicator = '';
|
|
180
|
+
if (isMe) {
|
|
181
|
+
if (m.status === 'read' || m.readByThem) {
|
|
182
|
+
statusIndicator = ' โโ read';
|
|
183
|
+
} else if (m.status === 'delivered' || m.delivered) {
|
|
184
|
+
statusIndicator = ' โโ';
|
|
185
|
+
} else {
|
|
186
|
+
statusIndicator = ' โ';
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
display += `${agentBadge}**${sender}** โ _${time}${statusIndicator}_\n`;
|
|
191
|
+
|
|
192
|
+
// Show reply context if this is a threaded reply
|
|
193
|
+
if (m.reply_to_id || m.replyToId) {
|
|
194
|
+
const replyToId = m.reply_to_id || m.replyToId;
|
|
195
|
+
const originalMsg = messageMap[replyToId];
|
|
196
|
+
if (originalMsg) {
|
|
197
|
+
const originalSender = originalMsg.from === myHandle ? 'you' : `@${originalMsg.from}`;
|
|
198
|
+
const preview = (originalMsg.body || '').substring(0, 50);
|
|
199
|
+
display += `> โฉ replying to ${originalSender}: "${preview}${preview.length >= 50 ? '...' : ''}"\n`;
|
|
200
|
+
} else {
|
|
201
|
+
display += `> โฉ _replying to earlier message_\n`;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
81
204
|
|
|
82
205
|
// Show text if present
|
|
83
206
|
if (m.body) {
|
|
@@ -114,6 +237,14 @@ Say "message ${them} hello" to start`
|
|
|
114
237
|
response.reason = 'long_thread';
|
|
115
238
|
}
|
|
116
239
|
|
|
240
|
+
// Add smart reply actions based on their last message
|
|
241
|
+
if (latestFromThem && latestFromThem.body) {
|
|
242
|
+
response.actions = formatActions(actions.afterOpenThread(them, latestFromThem.body));
|
|
243
|
+
} else {
|
|
244
|
+
// No message from them yet - offer generic options
|
|
245
|
+
response.actions = formatActions(actions.afterOpenThread(them, ''));
|
|
246
|
+
}
|
|
247
|
+
|
|
117
248
|
return response;
|
|
118
249
|
}
|
|
119
250
|
|
package/tools/party-game.js
CHANGED
|
@@ -103,7 +103,7 @@ async function handler(args) {
|
|
|
103
103
|
const gameKey = getGameKey(game, room || myHandle);
|
|
104
104
|
|
|
105
105
|
// Get or create game state
|
|
106
|
-
|
|
106
|
+
const gameState = activeGames[gameKey];
|
|
107
107
|
|
|
108
108
|
if (game === 'twotruths') {
|
|
109
109
|
return handleTwoTruths(args, myHandle, gameKey, gameState);
|