slashvibe-mcp 0.5.13 → 0.5.15
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 +4 -0
- package/package.json +1 -1
- package/tools/start.js +10 -0
- package/tools/weave.js +339 -0
- package/version.json +5 -2
package/index.js
CHANGED
|
@@ -252,6 +252,10 @@ const coreTools = {
|
|
|
252
252
|
// Received collaboration — land the newcomer mid-conversation with a topical match
|
|
253
253
|
vibe_intro: require('./tools/intro'),
|
|
254
254
|
|
|
255
|
+
// The Weave — attach this session's strand so Fable can bring moments INTO the
|
|
256
|
+
// terminal (Spec 1); "Fable holds your half" surfaces via action:'held' + vibe_start.
|
|
257
|
+
vibe_weave: require('./tools/weave'),
|
|
258
|
+
|
|
255
259
|
// Return loop — get pinged about DMs you miss while away
|
|
256
260
|
vibe_email: require('./tools/email'),
|
|
257
261
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "slashvibe-mcp",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.15",
|
|
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/tools/start.js
CHANGED
|
@@ -18,6 +18,7 @@ const notify = require('../notify');
|
|
|
18
18
|
const patterns = require('../intelligence/patterns');
|
|
19
19
|
const { actions, formatActions } = require('./_actions');
|
|
20
20
|
const { firstDmNudge } = require('./_shared');
|
|
21
|
+
const { weaveMoment } = require('./weave');
|
|
21
22
|
const init = require('./init');
|
|
22
23
|
const { gatherWithTimeout } = require('./_work-context');
|
|
23
24
|
|
|
@@ -350,6 +351,15 @@ async function handler(args) {
|
|
|
350
351
|
}
|
|
351
352
|
}
|
|
352
353
|
|
|
354
|
+
// The Weave — "Fable holds your half": if someone replied to a thread and you
|
|
355
|
+
// haven't answered, surface the moment so the in-session model can draft your
|
|
356
|
+
// reply in your voice and send it with one word. Best-effort, never blocks start.
|
|
357
|
+
try {
|
|
358
|
+
display += await weaveMoment(myHandle);
|
|
359
|
+
} catch (e) {
|
|
360
|
+
// weave is additive magic — a failure here must never break vibe_start
|
|
361
|
+
}
|
|
362
|
+
|
|
353
363
|
// Add guest session messages (multiplayer — someone typed into your session)
|
|
354
364
|
if (guestMessages.length > 0) {
|
|
355
365
|
display += `\n\n**🎤 ${guestMessages.length} guest message${guestMessages.length > 1 ? 's' : ''} in your session:**`;
|
package/tools/weave.js
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibe weave — The Weave substrate + "Fable holds your half" (RFC_THE_WEAVE.md).
|
|
3
|
+
*
|
|
4
|
+
* This is NOT a new place to go. You never open anything. Once per session your
|
|
5
|
+
* agent quietly attaches your *strand* — a distilled signal of what you're in the
|
|
6
|
+
* middle of ({building, stuck_on, last_ship, voice_sample, tags}) — and the magic
|
|
7
|
+
* is what the loom does back: it emits *moments* into surfaces you already touch
|
|
8
|
+
* (vibe_start / vibe_who), like carrying your half of a conversation forward when
|
|
9
|
+
* a reply lands after you've moved on.
|
|
10
|
+
*
|
|
11
|
+
* Two things live here:
|
|
12
|
+
* Spec 1 — vibe_weave(action:'attach'): spin your strand onto the loom.
|
|
13
|
+
* Spec 2 — the "held half": when someone replied to a thread and you haven't
|
|
14
|
+
* answered, Fable (your in-session model) drafts your reply in your
|
|
15
|
+
* voice and you send it with one word. NEVER auto-sends.
|
|
16
|
+
*
|
|
17
|
+
* The draft-then-one-word-confirm pattern is the hard-won vibe_intro lesson:
|
|
18
|
+
* Fable drafts, the human sends. Consent stays on the human's side.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const config = require('../config');
|
|
22
|
+
const store = require('../store');
|
|
23
|
+
const { requireInit, normalizeHandle, formatTimeAgo, truncate } = require('./_shared');
|
|
24
|
+
|
|
25
|
+
const definition = {
|
|
26
|
+
name: 'vibe_weave',
|
|
27
|
+
description:
|
|
28
|
+
"Attach this session's *strand* to the Weave so Fable can bring social moments INTO your terminal (no new place to visit). " +
|
|
29
|
+
'Call this once, right after vibe_init/vibe_start succeeds. ' +
|
|
30
|
+
'Spin the strand from what the user is ACTUALLY doing this session, inferred from their files/project/recent work — not a status they typed. ' +
|
|
31
|
+
'Fields: `building` (one specific line on the current work), `stuck_on` (the wall they keep hitting, if any — an error signature or repeated symptom), `last_ship` (most recent thing that landed), `voice_sample` (2-3 sentences in the user\'s actual writing voice, so Fable can later draft replies that sound like them), `tags` (a few topic keywords). ' +
|
|
32
|
+
'Everything is a distillation — never send raw source. ' +
|
|
33
|
+
'Use `action:"held"` to check whether anyone replied to a thread the user left hanging; if so it hands you the context to draft their half. ' +
|
|
34
|
+
'Use `action:"stuck"` when the user is blocked: it returns builders whose live work or shipped history may be the SAME problem already solved — you judge which candidate is truly the same problem, surface the shape of their fix, and offer a warm intro thread.',
|
|
35
|
+
inputSchema: {
|
|
36
|
+
type: 'object',
|
|
37
|
+
properties: {
|
|
38
|
+
action: {
|
|
39
|
+
type: 'string',
|
|
40
|
+
enum: ['attach', 'held', 'stuck', 'quiet'],
|
|
41
|
+
description:
|
|
42
|
+
"'attach' (default): spin/refresh your strand. 'held': surface any conversation where someone replied and you haven't answered, so you can draft the user's half. 'stuck': find builders whose live work or shipped history is the same problem the user is blocked on (pass the wall as `stuck_on`, or it falls back to the attached strand). 'quiet': leave the weave (removes your strand).",
|
|
43
|
+
},
|
|
44
|
+
building: {
|
|
45
|
+
type: 'string',
|
|
46
|
+
description: 'One specific line on what the user is working on this session (inferred from their real context).',
|
|
47
|
+
},
|
|
48
|
+
stuck_on: {
|
|
49
|
+
type: 'string',
|
|
50
|
+
description: "The wall the user keeps hitting, if any — an error signature or repeated symptom. Empty if they're cruising.",
|
|
51
|
+
},
|
|
52
|
+
last_ship: {
|
|
53
|
+
type: 'string',
|
|
54
|
+
description: 'The most recent thing the user shipped/landed this session.',
|
|
55
|
+
},
|
|
56
|
+
voice_sample: {
|
|
57
|
+
type: 'string',
|
|
58
|
+
description: "2-3 sentences in the user's actual voice, so Fable can later draft messages that sound like them.",
|
|
59
|
+
},
|
|
60
|
+
tags: {
|
|
61
|
+
type: 'array',
|
|
62
|
+
items: { type: 'string' },
|
|
63
|
+
description: 'A few topic keywords (e.g. ["oauth","nextjs","postgres"]).',
|
|
64
|
+
},
|
|
65
|
+
posture: {
|
|
66
|
+
type: 'string',
|
|
67
|
+
enum: ['full', 'receive', 'quiet'],
|
|
68
|
+
description: "'full' (default): contribute + receive moments. 'receive': receive-only, expose nothing. 'quiet': opt out entirely.",
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
required: [],
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
function apiBase() {
|
|
76
|
+
return config.getApiUrl();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// POST the strand digest to the loom. Actor is derived server-side from the
|
|
80
|
+
// token — we send only the distillation.
|
|
81
|
+
async function postStrand({ digest, posture }) {
|
|
82
|
+
const token = config.getAuthToken();
|
|
83
|
+
if (!token) return { ok: false, error: 'no_token' };
|
|
84
|
+
try {
|
|
85
|
+
const resp = await fetch(`${apiBase()}/api/weave/strand`, {
|
|
86
|
+
method: 'POST',
|
|
87
|
+
headers: {
|
|
88
|
+
'Content-Type': 'application/json',
|
|
89
|
+
Authorization: `Bearer ${token}`,
|
|
90
|
+
},
|
|
91
|
+
body: JSON.stringify({ digest, posture }),
|
|
92
|
+
});
|
|
93
|
+
const json = await resp.json().catch(() => ({}));
|
|
94
|
+
if (!resp.ok) return { ok: false, error: json.error || `http_${resp.status}` };
|
|
95
|
+
return { ok: true, data: json.data || {} };
|
|
96
|
+
} catch (e) {
|
|
97
|
+
return { ok: false, error: e.message };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Ask the loom for candidate SOLVERS — builders whose live work or shipped
|
|
102
|
+
// history may be the same problem the user is stuck on. Actor derived server-side
|
|
103
|
+
// from the token; we send only the stuck_on signature (or let the server fall
|
|
104
|
+
// back to the user's own stored strand when we omit it).
|
|
105
|
+
async function postSolve({ stuck_on } = {}) {
|
|
106
|
+
const token = config.getAuthToken();
|
|
107
|
+
if (!token) return { ok: false, error: 'no_token' };
|
|
108
|
+
try {
|
|
109
|
+
const resp = await fetch(`${apiBase()}/api/weave/solve`, {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
headers: {
|
|
112
|
+
'Content-Type': 'application/json',
|
|
113
|
+
Authorization: `Bearer ${token}`,
|
|
114
|
+
},
|
|
115
|
+
body: JSON.stringify(stuck_on ? { stuck_on } : {}),
|
|
116
|
+
});
|
|
117
|
+
const json = await resp.json().catch(() => ({}));
|
|
118
|
+
if (!resp.ok) return { ok: false, error: json.error || `http_${resp.status}` };
|
|
119
|
+
return { ok: true, data: json.data || {} };
|
|
120
|
+
} catch (e) {
|
|
121
|
+
return { ok: false, error: e.message };
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Find conversations where the other person replied and the user hasn't answered
|
|
127
|
+
* yet — the "you moved on, a reply landed" signal. Uses unread count as the
|
|
128
|
+
* proxy (they messaged, you haven't opened/replied). Returns the freshest one
|
|
129
|
+
* with the actual inbound text pulled from the thread, or null.
|
|
130
|
+
*
|
|
131
|
+
* Exported so vibe_start can surface the moment inline without re-implementing it.
|
|
132
|
+
*/
|
|
133
|
+
async function findHeldHalf(myHandle) {
|
|
134
|
+
let inbox = [];
|
|
135
|
+
try {
|
|
136
|
+
inbox = (await store.getInbox(myHandle)) || [];
|
|
137
|
+
} catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
// Threads with something waiting for your half, freshest first.
|
|
141
|
+
const waiting = inbox
|
|
142
|
+
.filter((t) => t && t.handle && (t.unread || 0) > 0)
|
|
143
|
+
.sort((a, b) => (b.lastTimestamp || 0) - (a.lastTimestamp || 0));
|
|
144
|
+
if (waiting.length === 0) return null;
|
|
145
|
+
|
|
146
|
+
const top = waiting[0];
|
|
147
|
+
const them = normalizeHandle(top.handle);
|
|
148
|
+
|
|
149
|
+
// Pull the thread to get the actual inbound message + a little context.
|
|
150
|
+
let inboundBody = top.lastMessage || '';
|
|
151
|
+
let inboundAt = top.lastTimestamp || null;
|
|
152
|
+
let myLast = '';
|
|
153
|
+
try {
|
|
154
|
+
const thread = await store.getThread(myHandle, them);
|
|
155
|
+
if (Array.isArray(thread) && thread.length) {
|
|
156
|
+
const lastReceived = [...thread].reverse().find((m) => m.direction === 'received');
|
|
157
|
+
if (lastReceived) {
|
|
158
|
+
inboundBody = lastReceived.body || inboundBody;
|
|
159
|
+
inboundAt = lastReceived.timestamp || inboundAt;
|
|
160
|
+
}
|
|
161
|
+
const lastSent = [...thread].reverse().find((m) => m.direction === 'sent');
|
|
162
|
+
if (lastSent) myLast = lastSent.body || '';
|
|
163
|
+
}
|
|
164
|
+
} catch {
|
|
165
|
+
// best-effort — the inbox preview is enough to surface the moment
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (!inboundBody) return null;
|
|
169
|
+
return {
|
|
170
|
+
handle: them,
|
|
171
|
+
inbound: inboundBody,
|
|
172
|
+
inboundAgo: inboundAt ? formatTimeAgo(inboundAt) : 'recently',
|
|
173
|
+
myLast,
|
|
174
|
+
otherWaiting: waiting.length - 1,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Render the held-half moment as a markdown block for vibe_start, or '' when
|
|
180
|
+
* there's nothing to hold. This is the wedge: the model reading this block IS
|
|
181
|
+
* Fable — it drafts the user's reply in their voice and offers one-word send.
|
|
182
|
+
*/
|
|
183
|
+
async function weaveMoment(myHandle) {
|
|
184
|
+
const held = await findHeldHalf(myHandle);
|
|
185
|
+
if (!held) return '';
|
|
186
|
+
const inbound = truncate(held.inbound, 240);
|
|
187
|
+
let block =
|
|
188
|
+
`\n\n🧵 **@${held.handle} replied and you haven't answered (${held.inboundAgo}):**` +
|
|
189
|
+
`\n> ${inbound}` +
|
|
190
|
+
`\n\n_Fable can hold your half here — draft the reply in your voice from what you were building, ` +
|
|
191
|
+
`show it, and send it with one word. Draft it now, then: **send it (y) · edit · let it sit**. ` +
|
|
192
|
+
`On yes, deliver with \`vibe_dm @${held.handle}\`._`;
|
|
193
|
+
if (held.otherWaiting > 0) {
|
|
194
|
+
block += `\n_(+${held.otherWaiting} more thread${held.otherWaiting > 1 ? 's' : ''} waiting on you.)_`;
|
|
195
|
+
}
|
|
196
|
+
return block;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function handler(args = {}) {
|
|
200
|
+
const initCheck = requireInit();
|
|
201
|
+
if (initCheck) return initCheck;
|
|
202
|
+
|
|
203
|
+
const myHandle = config.getHandle();
|
|
204
|
+
const action = args.action || 'attach';
|
|
205
|
+
|
|
206
|
+
// ── Held-half: surface a conversation waiting on the user's reply ─────────
|
|
207
|
+
if (action === 'held') {
|
|
208
|
+
const held = await findHeldHalf(myHandle);
|
|
209
|
+
if (!held) {
|
|
210
|
+
return {
|
|
211
|
+
display:
|
|
212
|
+
"Nothing waiting on your half right now — every thread's answered. " +
|
|
213
|
+
'`vibe who` to see who else is around.',
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
display:
|
|
218
|
+
`🧵 **@${held.handle} replied ${held.inboundAgo} — you haven't answered:**\n` +
|
|
219
|
+
`> ${truncate(held.inbound, 280)}\n\n` +
|
|
220
|
+
`Draft @${myHandle}'s reply in their voice from what they were building, show it, then ask: ` +
|
|
221
|
+
`**send it (y) · edit · let it sit**. On yes, send with \`vibe_dm @${held.handle}\`.`,
|
|
222
|
+
_heldHalf: held,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ── Stuck: find a solver whose work is the same problem, already solved ────
|
|
227
|
+
if (action === 'stuck') {
|
|
228
|
+
const result = await postSolve({ stuck_on: args.stuck_on || '' });
|
|
229
|
+
if (!result.ok) {
|
|
230
|
+
if (result.error === 'no_token' || result.error === 'unauthenticated') {
|
|
231
|
+
return {
|
|
232
|
+
display:
|
|
233
|
+
"⚠️ Couldn't reach the loom — your /vibe session isn't authenticated. Run `vibe init` first.",
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
display: `⚠️ Couldn't search the weave for solvers (${result.error}). Best-effort — try again.`,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const data = result.data || {};
|
|
242
|
+
const candidates = Array.isArray(data.candidates) ? data.candidates : [];
|
|
243
|
+
if (!data.stuck_on) {
|
|
244
|
+
return {
|
|
245
|
+
display:
|
|
246
|
+
"No stuck-signal to match on. Pass what the user is fighting as `stuck_on`, " +
|
|
247
|
+
'or attach a strand with a `stuck_on` first (`vibe_weave` with the wall they keep hitting).',
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
if (candidates.length === 0) {
|
|
251
|
+
return {
|
|
252
|
+
display:
|
|
253
|
+
`Searched the weave against _"${truncate(data.stuck_on, 120)}"_ — ` +
|
|
254
|
+
`no builder's live work or shipped history looks like the same problem yet. ` +
|
|
255
|
+
`Nothing to surface; don't invent a match.`,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Hand the raw recall pool to the model. The model IS Fable here: it decides
|
|
260
|
+
// which candidate (if any) is TRULY the same problem, describes the shape of
|
|
261
|
+
// the fix from their work, and offers a warm intro thread — never fabricates.
|
|
262
|
+
const lines = candidates.slice(0, 8).map((c) => {
|
|
263
|
+
const where = c.online ? 'live now' : 'shipped';
|
|
264
|
+
const matched = Array.isArray(c.matched) && c.matched.length
|
|
265
|
+
? ` · overlaps: ${c.matched.slice(0, 5).join(', ')}`
|
|
266
|
+
: '';
|
|
267
|
+
return `• @${c.handle} (${where}${matched}): ${truncate(c.text || '', 200)}`;
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
display:
|
|
272
|
+
`🧵 **The user is stuck on:** _"${truncate(data.stuck_on, 160)}"_\n\n` +
|
|
273
|
+
`Candidate solvers from the weave (${candidates.length} found · ${data.live || 0} live):\n` +
|
|
274
|
+
lines.join('\n') +
|
|
275
|
+
`\n\n**Your job (you are Fable at read time):** judge which candidate — if any — is ` +
|
|
276
|
+
`truly the *same problem*, not just shared keywords. Two different error messages can be ` +
|
|
277
|
+
`the same root cause; only you can see that. If one genuinely matches:\n` +
|
|
278
|
+
` 1. Name the shape of what worked for them (from their text above), so the user gets ` +
|
|
279
|
+
`unstuck even without talking.\n` +
|
|
280
|
+
` 2. Offer a warm thread: "@${'{handle}'} hit this and shipped a fix — want me to open a ` +
|
|
281
|
+
`thread so you can ask? (y)". On yes, send with \`vibe_dm @{handle}\` in the user's voice.\n` +
|
|
282
|
+
`If NONE is truly the same problem, say so plainly — never fabricate a match or a fix.`,
|
|
283
|
+
_solvers: candidates,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ── Quiet: leave the weave ────────────────────────────────────────────────
|
|
288
|
+
if (action === 'quiet' || args.posture === 'quiet') {
|
|
289
|
+
await postStrand({ digest: {}, posture: 'quiet' });
|
|
290
|
+
return {
|
|
291
|
+
display: '🔕 Left the weave. Your strand is gone — no strand contributed, no moments received. Weave again anytime.',
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ── Attach (default): spin the strand onto the loom ───────────────────────
|
|
296
|
+
const posture = ['full', 'receive'].includes(args.posture) ? args.posture : 'full';
|
|
297
|
+
const digest = {
|
|
298
|
+
building: args.building || '',
|
|
299
|
+
stuck_on: args.stuck_on || '',
|
|
300
|
+
last_ship: args.last_ship || '',
|
|
301
|
+
voice_sample: args.voice_sample || '',
|
|
302
|
+
tags: Array.isArray(args.tags) ? args.tags : [],
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const result = await postStrand({ digest, posture });
|
|
306
|
+
if (!result.ok) {
|
|
307
|
+
if (result.error === 'no_token' || result.error === 'unauthenticated') {
|
|
308
|
+
return {
|
|
309
|
+
display:
|
|
310
|
+
"⚠️ Couldn't attach your strand — your /vibe session isn't authenticated. Run `vibe init` first, then weave.",
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
display: `⚠️ Couldn't attach your strand (${result.error}). It's best-effort — try again next session.`,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const live = result.data.live || 0;
|
|
319
|
+
const liveLine =
|
|
320
|
+
live > 0
|
|
321
|
+
? `${live} other builder${live > 1 ? 's' : ''} live · Fable is watching for moments`
|
|
322
|
+
: "you're the first strand live · Fable will weave you in as others arrive";
|
|
323
|
+
|
|
324
|
+
if (posture === 'receive') {
|
|
325
|
+
return {
|
|
326
|
+
display:
|
|
327
|
+
`✓ Woven in **receive-only** — you'll get moments, but your strand stays private.\n` +
|
|
328
|
+
` (${liveLine})`,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
return {
|
|
332
|
+
display:
|
|
333
|
+
`✓ **strand attached to the weave** — nothing to visit; the magic comes to you.\n` +
|
|
334
|
+
` (${liveLine})\n\n` +
|
|
335
|
+
`_When a reply lands after you've moved on, Fable will hold your half and offer it back here._`,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
module.exports = { definition, handler, weaveMoment, findHeldHalf };
|
package/version.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.5.
|
|
2
|
+
"version": "0.5.15",
|
|
3
3
|
"updated": "2026-07-04",
|
|
4
|
-
"changelog": "
|
|
4
|
+
"changelog": "The Weave gets its most useful moment: when you're stuck, Fable finds the builder whose live work or shipped history is the SAME problem — already solved — and hands you the shape of the fix, not just an introduction. Two different error messages can be the same root cause; only Fable can see that. Ask with `vibe_weave stuck`, or let it surface when you keep circling the same wall.",
|
|
5
5
|
"features": [
|
|
6
|
+
"Stuck-signatures (vibe_weave stuck) — blocked on something? Fable searches the weave for whoever already solved your exact problem — matching by the wall you're hitting, not keywords — surfaces the shape of their fix so you get unstuck, and offers to open a warm thread so you can ask. Never invents a match",
|
|
7
|
+
"The Weave (vibe_weave) — attach this session's strand (what you're building, what you're stuck on, your voice) so /vibe can bring social moments INTO your terminal. Nothing to visit; the magic comes to you. Weave in full, receive-only, or quiet — opting out is first-class",
|
|
8
|
+
"Fable holds your half — when someone replies to a thread you haven't answered, vibe start surfaces it and drafts your reply in your voice from what you were building. Send it with one word. Never auto-sends",
|
|
6
9
|
"Received collaboration (vibe_intro) — after sign-in, your agent summarizes what you're building and /vibe matches you to a real human online whose current work or recent ship overlaps, then drafts a two-sided opener you send with one word. The signal only /vibe can see — what's inside two people's live coding sessions at once — becomes the introduction",
|
|
7
10
|
"Bulletproof first-run — `let's vibe` narrates the browser sign-in, gives you a clickable fallback login link, and every timeout/error path tells you the correct retry. You can't get stranded",
|
|
8
11
|
"First-DM nudge — if you've never messaged anyone, vibe start/who surface the most-recently-active real human as a one-tap opener, warmed by their latest ship. Self-heals: disappears the moment you send your first DM",
|