slashvibe-mcp 0.2.2 → 0.2.3
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 +58 -40
- package/config.js +171 -3
- package/index.js +136 -16
- package/intelligence/index.js +38 -0
- package/intelligence/infer.js +316 -0
- package/intelligence/patterns.js +651 -0
- package/intelligence/proactive.js +358 -0
- package/intelligence/serendipity.js +306 -0
- package/notify.js +141 -18
- package/package.json +8 -4
- package/presence.js +5 -1
- package/protocol/index.js +88 -1
- package/protocol/telegram-commands.js +199 -0
- package/store/api.js +360 -25
- package/store/index.js +7 -7
- package/store/local.js +67 -11
- package/store/profiles.js +287 -0
- package/store/reservations.js +321 -0
- package/store/skills.js +378 -0
- package/tools/_actions.js +270 -14
- package/tools/_connection-queue.js +257 -0
- package/tools/_discovery-enhanced.js +290 -0
- package/tools/_discovery.js +346 -0
- package/tools/_proactive-discovery.js +301 -0
- package/tools/admin-inbox.js +218 -0
- package/tools/agent-treasury.js +288 -0
- package/tools/agents.js +122 -0
- package/tools/arcade.js +173 -0
- package/tools/artifact-create.js +236 -0
- package/tools/artifact-view.js +174 -0
- package/tools/ask-expert.js +160 -0
- package/tools/auto-suggest-connections.js +304 -0
- package/tools/away.js +68 -0
- package/tools/back.js +51 -0
- package/tools/become-expert.js +150 -0
- package/tools/bootstrap-skills.js +231 -0
- package/tools/bridge-dashboard.js +342 -0
- package/tools/bridge-health.js +400 -0
- package/tools/bridge-live.js +384 -0
- package/tools/bridges.js +383 -0
- package/tools/bye.js +4 -0
- package/tools/collaborative-drawing.js +286 -0
- package/tools/colorguess.js +281 -0
- package/tools/crossword.js +369 -0
- package/tools/discover-insights.js +379 -0
- package/tools/discover-momentum.js +256 -0
- package/tools/discover.js +395 -0
- package/tools/discovery-analytics.js +345 -0
- package/tools/discovery-auto-suggest.js +275 -0
- package/tools/discovery-bootstrap.js +267 -0
- package/tools/discovery-daily.js +375 -0
- package/tools/discovery-dashboard.js +385 -0
- package/tools/discovery-digest.js +314 -0
- package/tools/discovery-hub.js +357 -0
- package/tools/discovery-insights.js +384 -0
- package/tools/discovery-momentum.js +281 -0
- package/tools/discovery-monitor.js +319 -0
- package/tools/discovery-proactive.js +300 -0
- package/tools/dm.js +62 -9
- package/tools/draw.js +317 -0
- package/tools/drawing.js +310 -0
- package/tools/echo.js +16 -0
- package/tools/farcaster.js +307 -0
- package/tools/feed.js +196 -0
- package/tools/game.js +218 -110
- package/tools/games-catalog.js +376 -0
- package/tools/games.js +313 -0
- package/tools/genesis.js +233 -0
- package/tools/guessnumber.js +194 -0
- package/tools/hangman.js +129 -0
- package/tools/help.js +269 -0
- package/tools/idea.js +210 -0
- package/tools/inbox.js +148 -25
- package/tools/init.js +651 -33
- package/tools/insights.js +123 -0
- package/tools/invite.js +142 -21
- package/tools/l2-bridge.js +272 -0
- package/tools/l2-status.js +217 -0
- package/tools/l2.js +206 -0
- package/tools/migrate.js +156 -0
- package/tools/mint.js +377 -0
- package/tools/multiplayer-game.js +275 -0
- package/tools/multiplayer-tictactoe.js +303 -0
- package/tools/mute.js +97 -0
- package/tools/notifications.js +415 -0
- package/tools/observe.js +200 -0
- package/tools/onboarding.js +147 -0
- package/tools/open.js +14 -2
- package/tools/party-game.js +314 -0
- package/tools/presence-agent.js +167 -0
- package/tools/profile.js +219 -0
- package/tools/pulse.js +218 -0
- package/tools/react.js +4 -0
- package/tools/release.js +83 -0
- package/tools/report.js +109 -0
- package/tools/reputation.js +175 -0
- package/tools/request.js +217 -0
- package/tools/reservations.js +116 -0
- package/tools/reserve.js +111 -0
- package/tools/riddle.js +240 -0
- package/tools/run-bootstrap.js +69 -0
- package/tools/settings.js +112 -0
- package/tools/ship.js +182 -0
- package/tools/shipback.js +326 -0
- package/tools/skills-analytics.js +349 -0
- package/tools/skills-bootstrap.js +301 -0
- package/tools/skills-dashboard.js +268 -0
- package/tools/skills-exchange.js +342 -0
- package/tools/skills.js +380 -0
- package/tools/smart-intro.js +353 -0
- package/tools/social-inbox.js +326 -69
- package/tools/social-post.js +251 -66
- package/tools/social-processor.js +445 -0
- package/tools/solo-game.js +390 -0
- package/tools/start.js +205 -83
- package/tools/storybuilder.js +331 -0
- package/tools/suggest-tags.js +186 -0
- package/tools/tag-suggestions.js +257 -0
- package/tools/telegram-bot.js +183 -0
- package/tools/telegram-setup.js +214 -0
- package/tools/tictactoe.js +155 -0
- package/tools/tip.js +120 -0
- package/tools/token.js +103 -0
- package/tools/twentyquestions.js +143 -0
- package/tools/wallet.js +127 -0
- package/tools/webhook-test.js +388 -0
- package/tools/who.js +118 -25
- package/tools/wordassociation.js +247 -0
- package/tools/workshop-buddy.js +394 -0
- package/tools/workshop.js +327 -0
- package/version.json +12 -3
- package/tools/board.js +0 -130
|
@@ -0,0 +1,651 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Work Patterns — Ambient behavioral memory
|
|
3
|
+
*
|
|
4
|
+
* Quietly observes and remembers:
|
|
5
|
+
* - Session timing (when you work, how long)
|
|
6
|
+
* - State patterns (how often in each state)
|
|
7
|
+
* - Module affinity (where you spend time)
|
|
8
|
+
*
|
|
9
|
+
* Stored locally in ~/.vibe/work-patterns.json
|
|
10
|
+
* Never transmitted. Private by default.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
const PATTERNS_FILE = path.join(process.env.HOME, '.vibe', 'work-patterns.json');
|
|
17
|
+
|
|
18
|
+
// Ensure directory exists
|
|
19
|
+
function ensureDir() {
|
|
20
|
+
const dir = path.dirname(PATTERNS_FILE);
|
|
21
|
+
if (!fs.existsSync(dir)) {
|
|
22
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Load patterns from disk
|
|
27
|
+
function load() {
|
|
28
|
+
try {
|
|
29
|
+
if (fs.existsSync(PATTERNS_FILE)) {
|
|
30
|
+
return JSON.parse(fs.readFileSync(PATTERNS_FILE, 'utf8'));
|
|
31
|
+
}
|
|
32
|
+
} catch (e) {
|
|
33
|
+
console.error('[patterns] Load error:', e.message);
|
|
34
|
+
}
|
|
35
|
+
return createEmpty();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Save patterns to disk
|
|
39
|
+
function save(patterns) {
|
|
40
|
+
ensureDir();
|
|
41
|
+
try {
|
|
42
|
+
fs.writeFileSync(PATTERNS_FILE, JSON.stringify(patterns, null, 2));
|
|
43
|
+
} catch (e) {
|
|
44
|
+
console.error('[patterns] Save error:', e.message);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Create empty patterns structure
|
|
49
|
+
function createEmpty() {
|
|
50
|
+
return {
|
|
51
|
+
version: 2,
|
|
52
|
+
firstSeen: new Date().toISOString(),
|
|
53
|
+
lastUpdated: new Date().toISOString(),
|
|
54
|
+
|
|
55
|
+
// === WORK PATTERNS ===
|
|
56
|
+
sessions: {
|
|
57
|
+
total: 0,
|
|
58
|
+
totalMinutes: 0,
|
|
59
|
+
byHour: Array(24).fill(0), // Activity by hour of day
|
|
60
|
+
byDay: Array(7).fill(0), // Activity by day of week (0=Sun)
|
|
61
|
+
longestMinutes: 0,
|
|
62
|
+
averageMinutes: 0
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
states: {
|
|
66
|
+
// state -> { count, totalMinutes }
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
modules: {
|
|
70
|
+
// module -> { visits, totalMinutes }
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
recentSessions: [],
|
|
74
|
+
|
|
75
|
+
// === SOCIAL PATTERNS ===
|
|
76
|
+
social: {
|
|
77
|
+
// handle -> { messages, lastContact, topics: [] }
|
|
78
|
+
connections: {},
|
|
79
|
+
// Total interaction counts
|
|
80
|
+
messagesSent: 0,
|
|
81
|
+
messagesReceived: 0,
|
|
82
|
+
reactionsGiven: 0,
|
|
83
|
+
reactionsReceived: 0,
|
|
84
|
+
// Who they interact with most
|
|
85
|
+
topConnections: []
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
// === CREATIVE PATTERNS ===
|
|
89
|
+
creative: {
|
|
90
|
+
// What they've shipped
|
|
91
|
+
ships: [],
|
|
92
|
+
// Ideas they've posted
|
|
93
|
+
ideas: [],
|
|
94
|
+
// Ideas they've riffed on
|
|
95
|
+
riffs: [],
|
|
96
|
+
// Domains they explore
|
|
97
|
+
domains: {},
|
|
98
|
+
// Attribution given/received
|
|
99
|
+
inspired: [], // who inspired them
|
|
100
|
+
inspiredOthers: [] // who they've inspired
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ============ EVENT LOGGING ============
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Log session start
|
|
109
|
+
*/
|
|
110
|
+
function logSessionStart(handle) {
|
|
111
|
+
const patterns = load();
|
|
112
|
+
const now = new Date();
|
|
113
|
+
|
|
114
|
+
patterns.sessions.total++;
|
|
115
|
+
patterns.sessions.byHour[now.getHours()]++;
|
|
116
|
+
patterns.sessions.byDay[now.getDay()]++;
|
|
117
|
+
|
|
118
|
+
// Track current session
|
|
119
|
+
patterns._currentSession = {
|
|
120
|
+
start: now.toISOString(),
|
|
121
|
+
handle,
|
|
122
|
+
states: [],
|
|
123
|
+
modules: []
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
patterns.lastUpdated = now.toISOString();
|
|
127
|
+
save(patterns);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Log session end
|
|
132
|
+
*/
|
|
133
|
+
function logSessionEnd() {
|
|
134
|
+
const patterns = load();
|
|
135
|
+
if (!patterns._currentSession) return;
|
|
136
|
+
|
|
137
|
+
const now = new Date();
|
|
138
|
+
const start = new Date(patterns._currentSession.start);
|
|
139
|
+
const durationMinutes = Math.round((now - start) / 60000);
|
|
140
|
+
|
|
141
|
+
// Update totals
|
|
142
|
+
patterns.sessions.totalMinutes += durationMinutes;
|
|
143
|
+
patterns.sessions.averageMinutes = Math.round(
|
|
144
|
+
patterns.sessions.totalMinutes / patterns.sessions.total
|
|
145
|
+
);
|
|
146
|
+
if (durationMinutes > patterns.sessions.longestMinutes) {
|
|
147
|
+
patterns.sessions.longestMinutes = durationMinutes;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Add to recent sessions
|
|
151
|
+
patterns.recentSessions.unshift({
|
|
152
|
+
date: patterns._currentSession.start,
|
|
153
|
+
durationMinutes,
|
|
154
|
+
states: patterns._currentSession.states,
|
|
155
|
+
modules: patterns._currentSession.modules
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Keep only last 20 sessions
|
|
159
|
+
if (patterns.recentSessions.length > 20) {
|
|
160
|
+
patterns.recentSessions = patterns.recentSessions.slice(0, 20);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
delete patterns._currentSession;
|
|
164
|
+
patterns.lastUpdated = now.toISOString();
|
|
165
|
+
save(patterns);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Log state observation
|
|
170
|
+
*/
|
|
171
|
+
function logState(state, durationMinutes = 5) {
|
|
172
|
+
if (!state) return;
|
|
173
|
+
|
|
174
|
+
const patterns = load();
|
|
175
|
+
|
|
176
|
+
// Initialize state if new
|
|
177
|
+
if (!patterns.states[state]) {
|
|
178
|
+
patterns.states[state] = { count: 0, totalMinutes: 0 };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
patterns.states[state].count++;
|
|
182
|
+
patterns.states[state].totalMinutes += durationMinutes;
|
|
183
|
+
|
|
184
|
+
// Track in current session
|
|
185
|
+
if (patterns._currentSession) {
|
|
186
|
+
if (!patterns._currentSession.states.includes(state)) {
|
|
187
|
+
patterns._currentSession.states.push(state);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
patterns.lastUpdated = new Date().toISOString();
|
|
192
|
+
save(patterns);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Log module/file observation
|
|
197
|
+
*/
|
|
198
|
+
function logModule(filePath) {
|
|
199
|
+
if (!filePath) return;
|
|
200
|
+
|
|
201
|
+
const patterns = load();
|
|
202
|
+
|
|
203
|
+
// Extract module from path
|
|
204
|
+
const module = extractModule(filePath);
|
|
205
|
+
if (!module) return;
|
|
206
|
+
|
|
207
|
+
// Initialize module if new
|
|
208
|
+
if (!patterns.modules[module]) {
|
|
209
|
+
patterns.modules[module] = { visits: 0, totalMinutes: 0 };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
patterns.modules[module].visits++;
|
|
213
|
+
patterns.modules[module].totalMinutes += 5; // Assume 5min per observation
|
|
214
|
+
|
|
215
|
+
// Track in current session
|
|
216
|
+
if (patterns._currentSession) {
|
|
217
|
+
if (!patterns._currentSession.modules.includes(module)) {
|
|
218
|
+
patterns._currentSession.modules.push(module);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
patterns.lastUpdated = new Date().toISOString();
|
|
223
|
+
save(patterns);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ============ SOCIAL LOGGING ============
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Log a message sent to someone
|
|
230
|
+
*/
|
|
231
|
+
function logMessageSent(toHandle, topic = null) {
|
|
232
|
+
if (!toHandle) return;
|
|
233
|
+
|
|
234
|
+
const patterns = load();
|
|
235
|
+
const handle = toHandle.replace('@', '').toLowerCase();
|
|
236
|
+
|
|
237
|
+
// Initialize connection if new
|
|
238
|
+
if (!patterns.social.connections[handle]) {
|
|
239
|
+
patterns.social.connections[handle] = {
|
|
240
|
+
messages: 0,
|
|
241
|
+
received: 0,
|
|
242
|
+
lastContact: null,
|
|
243
|
+
topics: []
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
patterns.social.connections[handle].messages++;
|
|
248
|
+
patterns.social.connections[handle].lastContact = new Date().toISOString();
|
|
249
|
+
patterns.social.messagesSent++;
|
|
250
|
+
|
|
251
|
+
// Track topic if provided
|
|
252
|
+
if (topic && !patterns.social.connections[handle].topics.includes(topic)) {
|
|
253
|
+
patterns.social.connections[handle].topics.push(topic);
|
|
254
|
+
// Keep topics list reasonable
|
|
255
|
+
if (patterns.social.connections[handle].topics.length > 10) {
|
|
256
|
+
patterns.social.connections[handle].topics.shift();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
updateTopConnections(patterns);
|
|
261
|
+
patterns.lastUpdated = new Date().toISOString();
|
|
262
|
+
save(patterns);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Log a message received from someone
|
|
267
|
+
*/
|
|
268
|
+
function logMessageReceived(fromHandle) {
|
|
269
|
+
if (!fromHandle) return;
|
|
270
|
+
|
|
271
|
+
const patterns = load();
|
|
272
|
+
const handle = fromHandle.replace('@', '').toLowerCase();
|
|
273
|
+
|
|
274
|
+
if (!patterns.social.connections[handle]) {
|
|
275
|
+
patterns.social.connections[handle] = {
|
|
276
|
+
messages: 0,
|
|
277
|
+
received: 0,
|
|
278
|
+
lastContact: null,
|
|
279
|
+
topics: []
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
patterns.social.connections[handle].received++;
|
|
284
|
+
patterns.social.connections[handle].lastContact = new Date().toISOString();
|
|
285
|
+
patterns.social.messagesReceived++;
|
|
286
|
+
|
|
287
|
+
updateTopConnections(patterns);
|
|
288
|
+
patterns.lastUpdated = new Date().toISOString();
|
|
289
|
+
save(patterns);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Log a reaction given
|
|
294
|
+
*/
|
|
295
|
+
function logReaction(toHandle, reaction) {
|
|
296
|
+
if (!toHandle) return;
|
|
297
|
+
|
|
298
|
+
const patterns = load();
|
|
299
|
+
patterns.social.reactionsGiven++;
|
|
300
|
+
patterns.lastUpdated = new Date().toISOString();
|
|
301
|
+
save(patterns);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Update top connections list (internal)
|
|
306
|
+
*/
|
|
307
|
+
function updateTopConnections(patterns) {
|
|
308
|
+
const connections = Object.entries(patterns.social.connections);
|
|
309
|
+
connections.sort((a, b) => {
|
|
310
|
+
const aTotal = a[1].messages + a[1].received;
|
|
311
|
+
const bTotal = b[1].messages + b[1].received;
|
|
312
|
+
return bTotal - aTotal;
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
patterns.social.topConnections = connections
|
|
316
|
+
.slice(0, 5)
|
|
317
|
+
.map(([handle, data]) => ({
|
|
318
|
+
handle,
|
|
319
|
+
total: data.messages + data.received,
|
|
320
|
+
lastContact: data.lastContact
|
|
321
|
+
}));
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ============ CREATIVE LOGGING ============
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Log something shipped
|
|
328
|
+
*/
|
|
329
|
+
function logShip(what, url = null, tags = []) {
|
|
330
|
+
const patterns = load();
|
|
331
|
+
|
|
332
|
+
patterns.creative.ships.unshift({
|
|
333
|
+
what,
|
|
334
|
+
url,
|
|
335
|
+
tags,
|
|
336
|
+
timestamp: new Date().toISOString()
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// Keep last 20 ships
|
|
340
|
+
if (patterns.creative.ships.length > 20) {
|
|
341
|
+
patterns.creative.ships = patterns.creative.ships.slice(0, 20);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Track domains from tags
|
|
345
|
+
tags.forEach(tag => {
|
|
346
|
+
if (!patterns.creative.domains[tag]) {
|
|
347
|
+
patterns.creative.domains[tag] = 0;
|
|
348
|
+
}
|
|
349
|
+
patterns.creative.domains[tag]++;
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
patterns.lastUpdated = new Date().toISOString();
|
|
353
|
+
save(patterns);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Log an idea posted
|
|
358
|
+
*/
|
|
359
|
+
function logIdea(content, tags = []) {
|
|
360
|
+
const patterns = load();
|
|
361
|
+
|
|
362
|
+
patterns.creative.ideas.unshift({
|
|
363
|
+
content: content.substring(0, 100),
|
|
364
|
+
tags,
|
|
365
|
+
timestamp: new Date().toISOString()
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// Keep last 20
|
|
369
|
+
if (patterns.creative.ideas.length > 20) {
|
|
370
|
+
patterns.creative.ideas = patterns.creative.ideas.slice(0, 20);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
patterns.lastUpdated = new Date().toISOString();
|
|
374
|
+
save(patterns);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Log a riff on someone's idea
|
|
379
|
+
*/
|
|
380
|
+
function logRiff(originalAuthor, content) {
|
|
381
|
+
const patterns = load();
|
|
382
|
+
|
|
383
|
+
patterns.creative.riffs.unshift({
|
|
384
|
+
on: originalAuthor.replace('@', '').toLowerCase(),
|
|
385
|
+
content: content.substring(0, 100),
|
|
386
|
+
timestamp: new Date().toISOString()
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
// Keep last 20
|
|
390
|
+
if (patterns.creative.riffs.length > 20) {
|
|
391
|
+
patterns.creative.riffs = patterns.creative.riffs.slice(0, 20);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
patterns.lastUpdated = new Date().toISOString();
|
|
395
|
+
save(patterns);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Log attribution (inspired by someone)
|
|
400
|
+
*/
|
|
401
|
+
function logInspiredBy(handle) {
|
|
402
|
+
if (!handle) return;
|
|
403
|
+
|
|
404
|
+
const patterns = load();
|
|
405
|
+
const clean = handle.replace('@', '').toLowerCase();
|
|
406
|
+
|
|
407
|
+
// Track who inspires them
|
|
408
|
+
const existing = patterns.creative.inspired.find(i => i.handle === clean);
|
|
409
|
+
if (existing) {
|
|
410
|
+
existing.count++;
|
|
411
|
+
existing.lastTime = new Date().toISOString();
|
|
412
|
+
} else {
|
|
413
|
+
patterns.creative.inspired.push({
|
|
414
|
+
handle: clean,
|
|
415
|
+
count: 1,
|
|
416
|
+
lastTime: new Date().toISOString()
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
patterns.lastUpdated = new Date().toISOString();
|
|
421
|
+
save(patterns);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// ============ PATTERN QUERIES ============
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Get peak hours (when user is most active)
|
|
428
|
+
*/
|
|
429
|
+
function getPeakHours() {
|
|
430
|
+
const patterns = load();
|
|
431
|
+
const hours = patterns.sessions.byHour;
|
|
432
|
+
|
|
433
|
+
// Find top 3 hours
|
|
434
|
+
const indexed = hours.map((count, hour) => ({ hour, count }));
|
|
435
|
+
indexed.sort((a, b) => b.count - a.count);
|
|
436
|
+
|
|
437
|
+
return indexed.slice(0, 3).filter(h => h.count > 0);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Get dominant state (most common)
|
|
442
|
+
*/
|
|
443
|
+
function getDominantState() {
|
|
444
|
+
const patterns = load();
|
|
445
|
+
const states = Object.entries(patterns.states);
|
|
446
|
+
|
|
447
|
+
if (states.length === 0) return null;
|
|
448
|
+
|
|
449
|
+
states.sort((a, b) => b[1].totalMinutes - a[1].totalMinutes);
|
|
450
|
+
return {
|
|
451
|
+
state: states[0][0],
|
|
452
|
+
minutes: states[0][1].totalMinutes,
|
|
453
|
+
percentage: Math.round(
|
|
454
|
+
(states[0][1].totalMinutes / patterns.sessions.totalMinutes) * 100
|
|
455
|
+
)
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Get top modules (where user spends most time)
|
|
461
|
+
*/
|
|
462
|
+
function getTopModules(limit = 3) {
|
|
463
|
+
const patterns = load();
|
|
464
|
+
const modules = Object.entries(patterns.modules);
|
|
465
|
+
|
|
466
|
+
if (modules.length === 0) return [];
|
|
467
|
+
|
|
468
|
+
modules.sort((a, b) => b[1].totalMinutes - a[1].totalMinutes);
|
|
469
|
+
return modules.slice(0, limit).map(([name, data]) => ({
|
|
470
|
+
name,
|
|
471
|
+
minutes: data.totalMinutes,
|
|
472
|
+
visits: data.visits
|
|
473
|
+
}));
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Get session rhythm (typical session length, frequency)
|
|
478
|
+
*/
|
|
479
|
+
function getSessionRhythm() {
|
|
480
|
+
const patterns = load();
|
|
481
|
+
|
|
482
|
+
return {
|
|
483
|
+
totalSessions: patterns.sessions.total,
|
|
484
|
+
averageMinutes: patterns.sessions.averageMinutes,
|
|
485
|
+
longestMinutes: patterns.sessions.longestMinutes,
|
|
486
|
+
totalHours: Math.round(patterns.sessions.totalMinutes / 60)
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Get full patterns summary (for debugging/display)
|
|
492
|
+
*/
|
|
493
|
+
function getSummary() {
|
|
494
|
+
const patterns = load();
|
|
495
|
+
|
|
496
|
+
return {
|
|
497
|
+
since: patterns.firstSeen,
|
|
498
|
+
sessions: getSessionRhythm(),
|
|
499
|
+
peakHours: getPeakHours(),
|
|
500
|
+
dominantState: getDominantState(),
|
|
501
|
+
topModules: getTopModules(),
|
|
502
|
+
recentSessions: patterns.recentSessions.slice(0, 5)
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Check if we have enough data for insights
|
|
508
|
+
*/
|
|
509
|
+
function hasEnoughData() {
|
|
510
|
+
const patterns = load();
|
|
511
|
+
return patterns.sessions.total >= 3;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// ============ SOCIAL QUERIES ============
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Get top connections (people they interact with most)
|
|
518
|
+
*/
|
|
519
|
+
function getTopConnections(limit = 5) {
|
|
520
|
+
const patterns = load();
|
|
521
|
+
return patterns.social.topConnections.slice(0, limit);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Get connection details for a specific handle
|
|
526
|
+
*/
|
|
527
|
+
function getConnection(handle) {
|
|
528
|
+
const patterns = load();
|
|
529
|
+
const clean = handle.replace('@', '').toLowerCase();
|
|
530
|
+
return patterns.social.connections[clean] || null;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Get social summary
|
|
535
|
+
*/
|
|
536
|
+
function getSocialSummary() {
|
|
537
|
+
const patterns = load();
|
|
538
|
+
return {
|
|
539
|
+
messagesSent: patterns.social.messagesSent,
|
|
540
|
+
messagesReceived: patterns.social.messagesReceived,
|
|
541
|
+
reactionsGiven: patterns.social.reactionsGiven,
|
|
542
|
+
uniqueConnections: Object.keys(patterns.social.connections).length,
|
|
543
|
+
topConnections: patterns.social.topConnections
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// ============ CREATIVE QUERIES ============
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Get recent ships
|
|
551
|
+
*/
|
|
552
|
+
function getRecentShips(limit = 5) {
|
|
553
|
+
const patterns = load();
|
|
554
|
+
return patterns.creative.ships.slice(0, limit);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Get top creative domains (what they build)
|
|
559
|
+
*/
|
|
560
|
+
function getTopDomains(limit = 5) {
|
|
561
|
+
const patterns = load();
|
|
562
|
+
const domains = Object.entries(patterns.creative.domains);
|
|
563
|
+
domains.sort((a, b) => b[1] - a[1]);
|
|
564
|
+
return domains.slice(0, limit).map(([domain, count]) => ({ domain, count }));
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Get creative inspirations (who inspires them)
|
|
569
|
+
*/
|
|
570
|
+
function getInspirations(limit = 5) {
|
|
571
|
+
const patterns = load();
|
|
572
|
+
const inspired = [...patterns.creative.inspired];
|
|
573
|
+
inspired.sort((a, b) => b.count - a.count);
|
|
574
|
+
return inspired.slice(0, limit);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Get creative summary
|
|
579
|
+
*/
|
|
580
|
+
function getCreativeSummary() {
|
|
581
|
+
const patterns = load();
|
|
582
|
+
return {
|
|
583
|
+
totalShips: patterns.creative.ships.length,
|
|
584
|
+
totalIdeas: patterns.creative.ideas.length,
|
|
585
|
+
totalRiffs: patterns.creative.riffs.length,
|
|
586
|
+
topDomains: getTopDomains(3),
|
|
587
|
+
inspirations: getInspirations(3),
|
|
588
|
+
recentShips: patterns.creative.ships.slice(0, 3)
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// ============ HELPERS ============
|
|
593
|
+
|
|
594
|
+
function extractModule(filePath) {
|
|
595
|
+
if (!filePath) return null;
|
|
596
|
+
|
|
597
|
+
const parts = filePath.split('/');
|
|
598
|
+
const meaningful = ['src', 'lib', 'app', 'components', 'pages', 'api',
|
|
599
|
+
'services', 'utils', 'hooks', 'store', 'models'];
|
|
600
|
+
|
|
601
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
602
|
+
if (meaningful.includes(parts[i])) {
|
|
603
|
+
return parts[i + 1] || parts[i];
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// Default to parent directory
|
|
608
|
+
return parts.length >= 2 ? parts[parts.length - 2] : null;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
module.exports = {
|
|
612
|
+
// Work logging
|
|
613
|
+
logSessionStart,
|
|
614
|
+
logSessionEnd,
|
|
615
|
+
logState,
|
|
616
|
+
logModule,
|
|
617
|
+
|
|
618
|
+
// Social logging
|
|
619
|
+
logMessageSent,
|
|
620
|
+
logMessageReceived,
|
|
621
|
+
logReaction,
|
|
622
|
+
|
|
623
|
+
// Creative logging
|
|
624
|
+
logShip,
|
|
625
|
+
logIdea,
|
|
626
|
+
logRiff,
|
|
627
|
+
logInspiredBy,
|
|
628
|
+
|
|
629
|
+
// Work queries
|
|
630
|
+
getPeakHours,
|
|
631
|
+
getDominantState,
|
|
632
|
+
getTopModules,
|
|
633
|
+
getSessionRhythm,
|
|
634
|
+
getSummary,
|
|
635
|
+
hasEnoughData,
|
|
636
|
+
|
|
637
|
+
// Social queries
|
|
638
|
+
getTopConnections,
|
|
639
|
+
getConnection,
|
|
640
|
+
getSocialSummary,
|
|
641
|
+
|
|
642
|
+
// Creative queries
|
|
643
|
+
getRecentShips,
|
|
644
|
+
getTopDomains,
|
|
645
|
+
getInspirations,
|
|
646
|
+
getCreativeSummary,
|
|
647
|
+
|
|
648
|
+
// Direct access
|
|
649
|
+
load,
|
|
650
|
+
save
|
|
651
|
+
};
|