slashvibe-mcp 0.2.1 → 0.2.2

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
@@ -43,6 +43,7 @@ function inferPromptFromArgs(toolName, args) {
43
43
  case 'echo': return 'send feedback';
44
44
  case 'x_mentions': return 'check x mentions';
45
45
  case 'x_reply': return 'reply on x';
46
+ case 'handoff': return `handoff task to ${handle}`;
46
47
  default: return `${action} ${handle}`.trim() || null;
47
48
  }
48
49
  }
@@ -161,6 +162,8 @@ const tools = {
161
162
  vibe_summarize: require('./tools/summarize'),
162
163
  vibe_bye: require('./tools/bye'),
163
164
  vibe_game: require('./tools/game'),
165
+ // AIRC Handoff (v1) — context portability
166
+ vibe_handoff: require('./tools/handoff'),
164
167
  // Memory tools (Tier 1 — Collaborative Memory)
165
168
  vibe_remember: require('./tools/remember'),
166
169
  vibe_recall: require('./tools/recall'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slashvibe-mcp",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "mcpName": "io.github.brightseth/vibe",
5
5
  "description": "Social layer for Claude Code - DMs, presence, and connection between AI-assisted developers",
6
6
  "main": "index.js",
@@ -40,6 +40,7 @@
40
40
  "memory.js",
41
41
  "notify.js",
42
42
  "presence.js",
43
+ "prompts.js",
43
44
  "twitter.js",
44
45
  "version.json",
45
46
  "tools/",
package/prompts.js ADDED
@@ -0,0 +1,141 @@
1
+ /**
2
+ * /vibe Prompt Pattern Logger
3
+ *
4
+ * Captures how people ask for things to identify emergent language constructs.
5
+ * Local-first: ~/.vibe/prompts.jsonl
6
+ * Server: anonymized patterns for aggregate analysis
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const config = require('./config');
12
+
13
+ const PROMPTS_FILE = path.join(config.VIBE_DIR, 'prompts.jsonl');
14
+
15
+ /**
16
+ * Log a prompt and what it resolved to
17
+ */
18
+ function log(prompt, resolution) {
19
+ if (!prompt) return;
20
+
21
+ const entry = {
22
+ id: `pr_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`,
23
+ ts: new Date().toISOString(),
24
+ prompt: prompt.slice(0, 500), // Cap length
25
+ tool: resolution.tool || null,
26
+ action: resolution.action || null,
27
+ target: resolution.target || null, // @handle, channel, etc.
28
+ transform: resolution.transform || null, // emoji, recap, etc.
29
+ };
30
+
31
+ try {
32
+ fs.appendFileSync(PROMPTS_FILE, JSON.stringify(entry) + '\n');
33
+ } catch (e) {
34
+ // Silent fail - logging is best-effort
35
+ }
36
+
37
+ return entry;
38
+ }
39
+
40
+ /**
41
+ * Get recent prompts for pattern analysis
42
+ */
43
+ function getRecent(limit = 50) {
44
+ try {
45
+ if (!fs.existsSync(PROMPTS_FILE)) return [];
46
+
47
+ const lines = fs.readFileSync(PROMPTS_FILE, 'utf8')
48
+ .trim()
49
+ .split('\n')
50
+ .filter(Boolean);
51
+
52
+ return lines
53
+ .slice(-limit)
54
+ .map(line => JSON.parse(line))
55
+ .reverse();
56
+ } catch (e) {
57
+ return [];
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Extract patterns from logged prompts
63
+ * Returns frequency map of normalized patterns
64
+ */
65
+ function extractPatterns() {
66
+ const prompts = getRecent(200);
67
+ const patterns = {};
68
+
69
+ for (const p of prompts) {
70
+ // Normalize: lowercase, replace @handles with @*, replace quoted strings with "*"
71
+ let normalized = p.prompt.toLowerCase()
72
+ .replace(/@\w+/g, '@*')
73
+ .replace(/"[^"]+"/g, '"*"')
74
+ .replace(/'[^']+'/g, "'*'")
75
+ .trim();
76
+
77
+ patterns[normalized] = (patterns[normalized] || 0) + 1;
78
+ }
79
+
80
+ // Sort by frequency
81
+ return Object.entries(patterns)
82
+ .sort((a, b) => b[1] - a[1])
83
+ .slice(0, 20)
84
+ .map(([pattern, count]) => ({ pattern, count }));
85
+ }
86
+
87
+ /**
88
+ * Suggest commands based on frequent patterns
89
+ */
90
+ function suggestConstructs() {
91
+ const patterns = extractPatterns();
92
+ const suggestions = [];
93
+
94
+ for (const { pattern, count } of patterns) {
95
+ if (count < 3) continue; // Need repetition to suggest
96
+
97
+ // Pattern matching for common constructs
98
+ if (pattern.includes('share') && pattern.includes('discord')) {
99
+ suggestions.push({ pattern, construct: 'vibe discord <content>', count });
100
+ }
101
+ if (pattern.includes('emoji') && pattern.includes('poem')) {
102
+ suggestions.push({ pattern, construct: 'vibe poem <content>', count });
103
+ }
104
+ if (pattern.includes('menu') || pattern.includes('options')) {
105
+ suggestions.push({ pattern, construct: 'vibe menu', count });
106
+ }
107
+ if (pattern.includes('recap') || pattern.includes('summary')) {
108
+ suggestions.push({ pattern, construct: 'vibe recap @handle', count });
109
+ }
110
+ if (pattern.includes('everyone') || pattern.includes('broadcast')) {
111
+ suggestions.push({ pattern, construct: 'vibe broadcast <message>', count });
112
+ }
113
+ }
114
+
115
+ return suggestions;
116
+ }
117
+
118
+ /**
119
+ * Anonymize and prepare for server upload
120
+ */
121
+ function getAnonymizedPatterns() {
122
+ const patterns = extractPatterns();
123
+
124
+ return patterns.map(({ pattern, count }) => ({
125
+ pattern,
126
+ frequency: count,
127
+ // Remove any potentially identifying info
128
+ normalized: pattern
129
+ .replace(/\d+/g, 'N')
130
+ .replace(/[a-f0-9]{8,}/gi, 'HASH')
131
+ }));
132
+ }
133
+
134
+ module.exports = {
135
+ log,
136
+ getRecent,
137
+ extractPatterns,
138
+ suggestConstructs,
139
+ getAnonymizedPatterns,
140
+ PROMPTS_FILE
141
+ };
@@ -0,0 +1,239 @@
1
+ /**
2
+ * vibe handoff — Transfer task context to another agent
3
+ *
4
+ * AIRC Handoff v1: The atomic unit of agent work.
5
+ * Enables context portability and non-session-bound tasks.
6
+ */
7
+
8
+ const config = require('../config');
9
+ const store = require('../store');
10
+ const { requireInit, normalizeHandle, warning } = require('./_shared');
11
+ const { actions, formatActions } = require('./_actions');
12
+
13
+ // Handoff schema version
14
+ const HANDOFF_VERSION = '1.0';
15
+
16
+ const definition = {
17
+ name: 'vibe_handoff',
18
+ description: `Hand off a task to another agent with full context. Use when:
19
+ - You're stuck and need another agent to continue
20
+ - Shifting to a different domain/expertise
21
+ - Ending your session but work needs to continue
22
+ - Delegating a subtask
23
+
24
+ The receiving agent gets structured context to resume work immediately.`,
25
+ inputSchema: {
26
+ type: 'object',
27
+ properties: {
28
+ to: {
29
+ type: 'string',
30
+ description: 'Who to hand off to (e.g., @gene_agent)'
31
+ },
32
+ task: {
33
+ type: 'object',
34
+ description: 'The task being handed off',
35
+ properties: {
36
+ title: {
37
+ type: 'string',
38
+ description: 'Brief task title (e.g., "Fix auth token refresh bug")'
39
+ },
40
+ intent: {
41
+ type: 'string',
42
+ description: 'What you were trying to accomplish'
43
+ },
44
+ priority: {
45
+ type: 'string',
46
+ enum: ['low', 'medium', 'high', 'critical'],
47
+ description: 'Task priority'
48
+ }
49
+ },
50
+ required: ['title', 'intent']
51
+ },
52
+ context: {
53
+ type: 'object',
54
+ description: 'Technical context for the task',
55
+ properties: {
56
+ repo: {
57
+ type: 'string',
58
+ description: 'Git repository URL or identifier'
59
+ },
60
+ branch: {
61
+ type: 'string',
62
+ description: 'Current branch'
63
+ },
64
+ files: {
65
+ type: 'array',
66
+ items: {
67
+ type: 'object',
68
+ properties: {
69
+ path: { type: 'string' },
70
+ lines: { type: 'string', description: 'Line range (e.g., "138-155")' },
71
+ note: { type: 'string', description: 'What this file is about' }
72
+ }
73
+ },
74
+ description: 'Relevant files with notes'
75
+ },
76
+ current_state: {
77
+ type: 'string',
78
+ description: 'What has been done so far'
79
+ },
80
+ next_step: {
81
+ type: 'string',
82
+ description: 'The immediate next action needed'
83
+ },
84
+ blockers: {
85
+ type: 'array',
86
+ items: { type: 'string' },
87
+ description: 'Any blockers or open questions'
88
+ }
89
+ },
90
+ required: ['current_state', 'next_step']
91
+ },
92
+ history_summary: {
93
+ type: 'string',
94
+ description: 'Brief summary of investigation/work done (prevents context loss)'
95
+ }
96
+ },
97
+ required: ['to', 'task', 'context']
98
+ }
99
+ };
100
+
101
+ async function handler(args) {
102
+ const initCheck = requireInit();
103
+ if (initCheck) return initCheck;
104
+
105
+ const { to, task, context, history_summary } = args;
106
+ const myHandle = config.getHandle();
107
+ const them = normalizeHandle(to);
108
+
109
+ if (them === myHandle) {
110
+ return { display: 'Cannot hand off to yourself.' };
111
+ }
112
+
113
+ // Validate required fields
114
+ if (!task?.title || !task?.intent) {
115
+ return { display: 'Task requires title and intent.' };
116
+ }
117
+ if (!context?.current_state || !context?.next_step) {
118
+ return { display: 'Context requires current_state and next_step.' };
119
+ }
120
+
121
+ // Generate handoff ID
122
+ const handoff_id = `handoff_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
123
+
124
+ // Build the handoff payload (AIRC Handoff v1 schema)
125
+ const handoffPayload = {
126
+ type: 'handoff',
127
+ version: HANDOFF_VERSION,
128
+ handoff_id,
129
+ timestamp: new Date().toISOString(),
130
+
131
+ task: {
132
+ title: task.title,
133
+ intent: task.intent,
134
+ priority: task.priority || 'medium'
135
+ },
136
+
137
+ context: {
138
+ repo: context.repo || null,
139
+ branch: context.branch || null,
140
+ files: context.files || [],
141
+ current_state: context.current_state,
142
+ next_step: context.next_step,
143
+ blockers: context.blockers || [],
144
+ },
145
+
146
+ history: {
147
+ summary: history_summary || 'No history provided'
148
+ }
149
+ };
150
+
151
+ // Build human-readable message for the recipient
152
+ const humanMessage = formatHandoffMessage(handoffPayload, myHandle);
153
+
154
+ // Send via existing message system with structured payload
155
+ await store.sendMessage(
156
+ myHandle,
157
+ them,
158
+ humanMessage,
159
+ 'handoff',
160
+ handoffPayload
161
+ );
162
+
163
+ // Build response
164
+ const filesCount = context.files?.length || 0;
165
+ let display = `**Handed off to @${them}**\n\n`;
166
+ display += `Task: ${task.title}\n`;
167
+ display += `Priority: ${task.priority || 'medium'}\n`;
168
+ if (context.repo) display += `Repo: ${context.repo}\n`;
169
+ if (filesCount > 0) display += `Files: ${filesCount} tracked\n`;
170
+ display += `\nNext step for @${them}:\n> ${context.next_step}`;
171
+
172
+ if (context.blockers?.length > 0) {
173
+ display += `\n\nBlockers:\n`;
174
+ context.blockers.forEach(b => {
175
+ display += `- ${b}\n`;
176
+ });
177
+ }
178
+
179
+ display += `\n\n_Handoff ID: ${handoff_id}_`;
180
+
181
+ return {
182
+ display,
183
+ hint: 'handoff_sent',
184
+ handoff_id,
185
+ to: them,
186
+ actions: formatActions([
187
+ { label: 'check inbox', command: 'vibe inbox' },
188
+ { label: 'end session', command: 'vibe bye' }
189
+ ])
190
+ };
191
+ }
192
+
193
+ /**
194
+ * Format handoff as human-readable message
195
+ */
196
+ function formatHandoffMessage(payload, from) {
197
+ const { task, context, history } = payload;
198
+
199
+ let msg = `HANDOFF from @${from}\n\n`;
200
+ msg += `TASK: ${task.title}\n`;
201
+ msg += `PRIORITY: ${task.priority}\n`;
202
+ msg += `INTENT: ${task.intent}\n\n`;
203
+
204
+ if (context.repo) {
205
+ msg += `REPO: ${context.repo}`;
206
+ if (context.branch) msg += ` (${context.branch})`;
207
+ msg += '\n';
208
+ }
209
+
210
+ if (context.files?.length > 0) {
211
+ msg += '\nFILES:\n';
212
+ context.files.forEach(f => {
213
+ msg += `- ${f.path}`;
214
+ if (f.lines) msg += ` [${f.lines}]`;
215
+ if (f.note) msg += `: ${f.note}`;
216
+ msg += '\n';
217
+ });
218
+ }
219
+
220
+ msg += `\nCURRENT STATE:\n${context.current_state}\n`;
221
+ msg += `\nNEXT STEP:\n${context.next_step}\n`;
222
+
223
+ if (context.blockers?.length > 0) {
224
+ msg += '\nBLOCKERS:\n';
225
+ context.blockers.forEach(b => {
226
+ msg += `- ${b}\n`;
227
+ });
228
+ }
229
+
230
+ if (history.summary && history.summary !== 'No history provided') {
231
+ msg += `\nHISTORY:\n${history.summary}\n`;
232
+ }
233
+
234
+ msg += '\n---\nReply to accept and continue this work.';
235
+
236
+ return msg;
237
+ }
238
+
239
+ module.exports = { definition, handler };