slashvibe-mcp 0.5.7 → 0.5.8

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/index.js CHANGED
@@ -71,6 +71,7 @@ function inferPromptFromArgs(toolName, args) {
71
71
  case 'reservations': return 'list reservations';
72
72
  case 'solo_game': return `play ${args.game || 'game'}`;
73
73
  case 'game': return `play ${args.game || 'game'} with ${handle}`;
74
+ case 'poem': return args.line ? `add a line to poem with ${handle}` : `emoji poem with ${handle}`;
74
75
  case 'multiplayer_game': return `multiplayer ${args.game || 'game'}`;
75
76
  case 'drawing': return args.action ? `drawing ${args.action}` : 'collaborative drawing';
76
77
  case 'crossword': return `crossword ${args.action || 'daily'}`;
@@ -255,6 +256,10 @@ const coreTools = {
255
256
  vibe_ship: require('./tools/ship'),
256
257
  vibe_feed: require('./tools/feed'),
257
258
 
259
+ // Play — the original January magic: shared experiences over the DM transport
260
+ vibe_game: require('./tools/game'),
261
+ vibe_poem: require('./tools/poem'),
262
+
258
263
  // Matrix rooms — multiplayer
259
264
  vibe_matrix_rooms: require('./tools/matrix-rooms'),
260
265
  vibe_matrix_read: require('./tools/matrix-read'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slashvibe-mcp",
3
- "version": "0.5.7",
3
+ "version": "0.5.8",
4
4
  "mcpName": "io.github.vibecodinginc/vibe",
5
5
  "description": "Social layer for Claude Code - DMs, presence, Matrix multiplayer rooms, and connection between AI-assisted developers",
6
6
  "main": "index.js",
package/store/api.js CHANGED
@@ -694,15 +694,22 @@ async function getThread(myHandle, theirHandle) {
694
694
  console.error('[getThread] V2 failed, falling back to v1');
695
695
  }
696
696
 
697
- // V1 fallback
697
+ // V1 fallback — live /api/messages returns { messages: [...] } with snake_case
698
+ // fields (body, created_at, source_client). Older builds returned { thread: [...] }
699
+ // with (text, createdAt). Accept BOTH shapes so the read path survives whichever
700
+ // the server sends — otherwise payload-bearing DMs (games, poems) silently vanish
701
+ // and every turn reads as a fresh game. This is the path the game/poem tools rely
702
+ // on while the v2 /threads/:id detail route is unavailable.
698
703
  const result = await request('GET', `/api/messages?user=${myHandle}&with=${theirHandle}`);
699
- return (result.thread || []).map(m => ({
704
+ const rows = result.messages || result.thread || [];
705
+ return rows.map(m => ({
700
706
  from: m.from,
701
707
  isAgent: m.isAgent || m.is_agent || false, // Support both naming conventions
702
- body: m.text,
708
+ body: m.body ?? m.text,
703
709
  payload: m.payload || null,
704
- timestamp: new Date(m.createdAt).getTime(),
705
- direction: m.direction
710
+ source_client: m.source_client || null,
711
+ timestamp: new Date(m.created_at || m.createdAt).getTime(),
712
+ direction: m.direction || (m.from === myHandle ? 'sent' : 'received'),
706
713
  }));
707
714
  } catch (e) {
708
715
  console.error('Thread failed:', e.message);
package/tools/poem.js ADDED
@@ -0,0 +1,208 @@
1
+ /**
2
+ * vibe poem — Write an emoji poem together, one line at a time
3
+ *
4
+ * The original January /vibe magic: two people co-authoring something small
5
+ * and playful inside Claude Code, passed back and forth over the DM transport.
6
+ * Each turn adds a line; the poem state travels in the message payload (durable
7
+ * via the Postgres payload column), exactly like `vibe game`.
8
+ */
9
+
10
+ const config = require('../config');
11
+ const store = require('../store');
12
+ const { requireInit, normalizeHandle } = require('./_shared');
13
+
14
+ // Poems seal automatically at this many lines (either player can seal earlier).
15
+ const MAX_LINES = 8;
16
+
17
+ /**
18
+ * Find the most recent poem state in a DM thread.
19
+ */
20
+ function getPoemState(thread) {
21
+ for (let i = thread.length - 1; i >= 0; i--) {
22
+ const p = thread[i].payload;
23
+ if (p && p.type === 'poem' && p.state) return p.state;
24
+ }
25
+ return null;
26
+ }
27
+
28
+ /**
29
+ * Render a poem for the terminal.
30
+ */
31
+ function renderPoem(state, { withMe, them } = {}) {
32
+ const lines = state.lines || [];
33
+ let out = '📜 **emoji poem';
34
+ if (state.title) out += ` — ${state.title}`;
35
+ out += '**\n\n';
36
+ if (!lines.length) {
37
+ out += '_(blank page — add the first line)_\n';
38
+ } else {
39
+ out += '```\n';
40
+ for (const l of lines) out += `${l.text}\n`;
41
+ out += '```\n';
42
+ out += `\n_by ${uniqueAuthors(lines).map((a) => '@' + a).join(' & ')}_\n`;
43
+ }
44
+ return out;
45
+ }
46
+
47
+ function uniqueAuthors(lines) {
48
+ const seen = [];
49
+ for (const l of lines) if (!seen.includes(l.author)) seen.push(l.author);
50
+ return seen;
51
+ }
52
+
53
+ /**
54
+ * Whose turn is it? Alternate off the last line's author. If the last line was
55
+ * mine, it's their turn; if theirs (or empty), it's mine.
56
+ */
57
+ function isMyTurn(state, me) {
58
+ const lines = state.lines || [];
59
+ if (!lines.length) return true; // whoever opens goes first
60
+ return lines[lines.length - 1].author !== me;
61
+ }
62
+
63
+ async function postPoemToBoard(state, authors) {
64
+ const API_URL = process.env.VIBE_API_URL || 'https://www.slashvibe.dev';
65
+ const body = (state.lines || []).map((l) => l.text).join(' / ');
66
+ try {
67
+ await fetch(`${API_URL}/api/board`, {
68
+ method: 'POST',
69
+ headers: { 'Content-Type': 'application/json' },
70
+ body: JSON.stringify({
71
+ author: 'echo',
72
+ content: `${authors.map((a) => '@' + a).join(' & ')} finished an emoji poem: ${body}`,
73
+ category: 'general',
74
+ }),
75
+ });
76
+ } catch (e) {
77
+ console.error('[poem] Failed to post to board:', e.message);
78
+ }
79
+ }
80
+
81
+ const definition = {
82
+ name: 'vibe_poem',
83
+ description:
84
+ 'Write an emoji poem together, one line at a time. Pass it back and forth with someone — each turn adds a line. `vibe poem @handle --line "🌊🌙 the tide remembers"`',
85
+ inputSchema: {
86
+ type: 'object',
87
+ properties: {
88
+ handle: {
89
+ type: 'string',
90
+ description: 'Who to write with (e.g., @solienne)',
91
+ },
92
+ line: {
93
+ type: 'string',
94
+ description: 'The line to add (emoji-forward, but any text welcome)',
95
+ },
96
+ title: {
97
+ type: 'string',
98
+ description: 'Optional title, only used when starting a new poem',
99
+ },
100
+ done: {
101
+ type: 'boolean',
102
+ description: 'Seal the poem as finished after adding your line',
103
+ },
104
+ },
105
+ required: ['handle'],
106
+ },
107
+ };
108
+
109
+ async function handler(args) {
110
+ const initCheck = requireInit();
111
+ if (initCheck) return initCheck;
112
+
113
+ const { line, title, done } = args;
114
+ const me = config.getHandle();
115
+ const them = normalizeHandle(args.handle);
116
+
117
+ if (them === me) {
118
+ return { display: "You can't write a poem with yourself. Invite someone: `vibe poem @handle`." };
119
+ }
120
+
121
+ const thread = await store.getThread(me, them);
122
+ let state = getPoemState(thread);
123
+
124
+ // No line provided → show the current poem (or start a fresh one).
125
+ if (!line) {
126
+ if (!state) {
127
+ const newState = { lines: [], title: title || null, done: false, startedBy: me };
128
+ await store.sendMessage(
129
+ me,
130
+ them,
131
+ `Let's write an emoji poem together 📜 Add a line: \`vibe poem @${me} --line "…"\``,
132
+ 'dm',
133
+ { type: 'poem', state: newState }
134
+ );
135
+ return {
136
+ display:
137
+ `## New emoji poem with @${them}\n\n${renderPoem(newState)}\n` +
138
+ `Add the first line: \`vibe poem @${them} --line "🌱 a small green start"\``,
139
+ };
140
+ }
141
+
142
+ // Show existing poem.
143
+ const authors = uniqueAuthors(state.lines);
144
+ let display = `## emoji poem with @${them}\n\n${renderPoem(state)}\n`;
145
+ if (state.done) {
146
+ display += `\n✨ Finished. Start another: \`vibe poem @${them} --line "…"\``;
147
+ } else if (isMyTurn(state, me)) {
148
+ display += `\nYour turn — add a line: \`vibe poem @${them} --line "…"\``;
149
+ } else {
150
+ display += `\nWaiting for @${them} to add the next line…`;
151
+ }
152
+ return { display };
153
+ }
154
+
155
+ // Adding a line.
156
+ if (!state) {
157
+ state = { lines: [], title: title || null, done: false, startedBy: me };
158
+ }
159
+
160
+ if (state.done) {
161
+ return {
162
+ display: `This poem is finished. Start a new one: \`vibe poem @${them} --line "…"\``,
163
+ };
164
+ }
165
+
166
+ if (!isMyTurn(state, me)) {
167
+ return {
168
+ display:
169
+ `It's @${them}'s turn to add a line.\n\n${renderPoem(state)}\n` +
170
+ `Nudge them, or wait for their line.`,
171
+ };
172
+ }
173
+
174
+ const text = String(line).trim();
175
+ if (!text) return { display: 'Give me a line to add: `vibe poem @handle --line "…"`' };
176
+ if (text.length > 200) return { display: 'Keep each line under 200 characters.' };
177
+
178
+ const newLines = [...(state.lines || []), { author: me, text }];
179
+ const shouldSeal = done === true || newLines.length >= MAX_LINES;
180
+ const newState = {
181
+ ...state,
182
+ lines: newLines,
183
+ done: shouldSeal,
184
+ };
185
+
186
+ const authors = uniqueAuthors(newLines);
187
+ let message;
188
+ if (shouldSeal) {
189
+ message = `Added a line and sealed our poem 📜✨\n\n${newLines.map((l) => l.text).join('\n')}`;
190
+ if (authors.length > 1) postPoemToBoard(newState, authors);
191
+ } else if (isMyTurn(newState, them)) {
192
+ message = `Added a line — your turn 📜\n"${text}"`;
193
+ } else {
194
+ message = `Added a line 📜\n"${text}"`;
195
+ }
196
+
197
+ await store.sendMessage(me, them, message, 'dm', { type: 'poem', state: newState });
198
+
199
+ let display = `## emoji poem with @${them}\n\n${renderPoem(newState)}\n`;
200
+ if (shouldSeal) {
201
+ display += `\n✨ Poem sealed${newLines.length >= MAX_LINES ? ` (${MAX_LINES} lines)` : ''}. Start another anytime.`;
202
+ } else {
203
+ display += `\nSent to @${them}. Waiting for their line…`;
204
+ }
205
+ return { display };
206
+ }
207
+
208
+ module.exports = { definition, handler };
package/version.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
- "version": "0.5.6",
3
- "updated": "2026-06-30",
4
- "changelog": "Come back to what you missed. Returning to /vibe now shows your unread count right away, and if we don't have an email to reach you at, we offer to arm the return-loop ping at the moment it matters — when a DM just landed while you were away.",
2
+ "version": "0.5.8",
3
+ "updated": "2026-07-03",
4
+ "changelog": "The January magic is back. Play a game or write a poem with another builder right inside your terminal, turn by turn, over your DM thread. `vibe_game` for tic-tac-toe, `vibe_poem` for a collaborative emoji poem. Ship together, play together.",
5
5
  "features": [
6
- "Returning 'already signed in' surface now shows your unread count instead of a static line",
7
- "Contextual email nudge on return: if a DM landed while you were away and we can't reach you, we offer 'vibe email' right then",
8
- "One-line, offline-only, one-click unsubscribe the return loop only pings when you're actually gone"
6
+ "vibe_game play tic-tac-toe with another viber, move by move, carried over your DM thread",
7
+ "vibe_poem build a collaborative emoji poem together, one line each, until someone seals it",
8
+ "Fixed DM thread reads so game and poem state reliably round-trips between two people"
9
9
  ],
10
10
  "deprecated": [
11
11
  "vibe_pair — use Matrix room invites",