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,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vibe_work_summary — Standalone tool for explicit context gathering
|
|
3
|
+
*
|
|
4
|
+
* Use this when you need to:
|
|
5
|
+
* - Draft a message about what you're working on
|
|
6
|
+
* - Share your progress with someone
|
|
7
|
+
* - Get context for composing a "ship" post
|
|
8
|
+
*
|
|
9
|
+
* This tool returns structured data about:
|
|
10
|
+
* - Git state (branch, recent commits, changed files)
|
|
11
|
+
* - Project info (name, type)
|
|
12
|
+
* - Pre-formatted suggestions ready for messages/presence
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const { gatherWorkContext } = require('./_work-context');
|
|
16
|
+
|
|
17
|
+
const definition = {
|
|
18
|
+
name: 'vibe_work_summary',
|
|
19
|
+
description: 'Get a summary of what you\'re working on (git state, project info). Use before composing messages about your work.',
|
|
20
|
+
inputSchema: {
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
detail_level: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
enum: ['brief', 'detailed'],
|
|
26
|
+
description: 'brief = one-liner, detailed = include commits/files'
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
async function handler(args) {
|
|
33
|
+
const { detail_level = 'brief' } = args;
|
|
34
|
+
|
|
35
|
+
// Gather all context
|
|
36
|
+
const context = gatherWorkContext();
|
|
37
|
+
|
|
38
|
+
// Build display based on detail level
|
|
39
|
+
let display = '## Work Context\n\n';
|
|
40
|
+
|
|
41
|
+
// Project info
|
|
42
|
+
display += `**Project:** ${context.project.name}`;
|
|
43
|
+
if (context.project.type !== 'unknown') {
|
|
44
|
+
display += ` (${context.project.type})`;
|
|
45
|
+
}
|
|
46
|
+
display += '\n';
|
|
47
|
+
|
|
48
|
+
// Git info
|
|
49
|
+
if (context.git) {
|
|
50
|
+
display += `**Branch:** ${context.git.branch}\n`;
|
|
51
|
+
|
|
52
|
+
if (detail_level === 'detailed') {
|
|
53
|
+
// Show recent commits
|
|
54
|
+
if (context.git.recentCommits.length > 0) {
|
|
55
|
+
display += '\n**Recent commits:**\n';
|
|
56
|
+
context.git.recentCommits.forEach(c => {
|
|
57
|
+
display += `- \`${c.hash}\` ${c.message}\n`;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Show changed files
|
|
62
|
+
if (context.git.changedFiles.length > 0) {
|
|
63
|
+
display += '\n**Changed files:**\n';
|
|
64
|
+
context.git.changedFiles.forEach(f => {
|
|
65
|
+
display += `- ${f}\n`;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Uncommitted changes indicator
|
|
70
|
+
if (context.git.hasUncommitted) {
|
|
71
|
+
display += '\n⚠️ _Uncommitted changes_\n';
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
display += '_Not in a git repository_\n';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Suggestions
|
|
79
|
+
display += '\n---\n\n';
|
|
80
|
+
display += '**Ready to use:**\n';
|
|
81
|
+
display += `- Brief: "${context.suggestions.brief}"\n`;
|
|
82
|
+
if (detail_level === 'detailed' && context.suggestions.detailed !== context.suggestions.brief) {
|
|
83
|
+
display += `- Detailed: "${context.suggestions.detailed}"\n`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
display += '\n_Use these in messages, status updates, or ship posts._';
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
display,
|
|
90
|
+
// Structured data for Claude to use programmatically
|
|
91
|
+
...context,
|
|
92
|
+
usage_hint: 'Use suggestions.brief for messages, suggestions.detailed for updates.'
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = { definition, handler };
|
package/version.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.3.
|
|
3
|
-
"updated": "2026-01-
|
|
4
|
-
"changelog": "
|
|
2
|
+
"version": "0.3.16",
|
|
3
|
+
"updated": "2026-01-26",
|
|
4
|
+
"changelog": "MCP cleanup: _shared utilities, version sync, deprecated tool archive, ESM->CommonJS",
|
|
5
5
|
"features": [
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
6
|
+
"Smart inbox checking - triggers on commits, tests, builds, natural breaks",
|
|
7
|
+
"Tech stack auto-detection for better connection matching",
|
|
8
|
+
"Enhanced streak system - 5 badge tiers, freeze preservation",
|
|
9
|
+
"Leaderboard 2.1 - category rankings, position tracking, movers",
|
|
10
|
+
"Featured sessions in browse",
|
|
11
|
+
"Gig recommendations based on proven skills",
|
|
12
|
+
"Mobile dashboard API for fast loading"
|
|
11
13
|
],
|
|
12
14
|
"breaking": false,
|
|
13
15
|
"updateUrl": "https://www.slashvibe.dev/api/version"
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Slash Vibe, Inc.
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
package/store/sqlite.js
DELETED
|
@@ -1,347 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SQLite Message Store - Local persistence for v2 messaging
|
|
3
|
-
*
|
|
4
|
-
* Shares database with Vibe Terminal app at ~/.vibecodings/sessions.db
|
|
5
|
-
* Schema matches src-tauri/src/db.rs exactly (LocalMessage struct)
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const Database = require('better-sqlite3');
|
|
9
|
-
const path = require('path');
|
|
10
|
-
const os = require('os');
|
|
11
|
-
const fs = require('fs');
|
|
12
|
-
const { randomUUID } = require('crypto');
|
|
13
|
-
|
|
14
|
-
const DB_PATH = path.join(os.homedir(), '.vibecodings', 'sessions.db');
|
|
15
|
-
|
|
16
|
-
class MessageStore {
|
|
17
|
-
constructor() {
|
|
18
|
-
// Ensure directory exists
|
|
19
|
-
const dir = path.dirname(DB_PATH);
|
|
20
|
-
if (!fs.existsSync(dir)) {
|
|
21
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
this.db = new Database(DB_PATH);
|
|
25
|
-
|
|
26
|
-
// Enable WAL mode for better concurrency with Tauri app
|
|
27
|
-
this.db.pragma('journal_mode = WAL');
|
|
28
|
-
this.db.pragma('busy_timeout = 5000');
|
|
29
|
-
this.db.pragma('synchronous = NORMAL');
|
|
30
|
-
|
|
31
|
-
// Ensure messages table exists (should already exist from Tauri, but just in case)
|
|
32
|
-
this.ensureSchema();
|
|
33
|
-
|
|
34
|
-
// Prepare statements for performance
|
|
35
|
-
this.prepareStatements();
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
ensureSchema() {
|
|
39
|
-
// This matches the Tauri schema + V2 Postgres fields
|
|
40
|
-
this.db.exec(`
|
|
41
|
-
CREATE TABLE IF NOT EXISTS messages (
|
|
42
|
-
local_id TEXT PRIMARY KEY,
|
|
43
|
-
server_id TEXT,
|
|
44
|
-
thread_id TEXT,
|
|
45
|
-
from_handle TEXT NOT NULL,
|
|
46
|
-
to_handle TEXT NOT NULL,
|
|
47
|
-
content TEXT NOT NULL,
|
|
48
|
-
created_at TEXT NOT NULL,
|
|
49
|
-
status TEXT NOT NULL CHECK(status IN ('pending', 'sent', 'delivered', 'read', 'failed')),
|
|
50
|
-
sent_at TEXT,
|
|
51
|
-
delivered_at TEXT,
|
|
52
|
-
read_at TEXT,
|
|
53
|
-
synced_at TEXT,
|
|
54
|
-
retry_count INTEGER DEFAULT 0
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
CREATE INDEX IF NOT EXISTS idx_messages_thread
|
|
58
|
-
ON messages(from_handle, to_handle, created_at);
|
|
59
|
-
|
|
60
|
-
CREATE INDEX IF NOT EXISTS idx_messages_thread_id
|
|
61
|
-
ON messages(thread_id);
|
|
62
|
-
|
|
63
|
-
CREATE INDEX IF NOT EXISTS idx_messages_server_id
|
|
64
|
-
ON messages(server_id);
|
|
65
|
-
|
|
66
|
-
CREATE INDEX IF NOT EXISTS idx_messages_status
|
|
67
|
-
ON messages(status);
|
|
68
|
-
|
|
69
|
-
CREATE INDEX IF NOT EXISTS idx_messages_synced
|
|
70
|
-
ON messages(synced_at);
|
|
71
|
-
`);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
prepareStatements() {
|
|
75
|
-
this.stmts = {
|
|
76
|
-
insert: this.db.prepare(`
|
|
77
|
-
INSERT OR REPLACE INTO messages
|
|
78
|
-
(local_id, server_id, thread_id, from_handle, to_handle, content, created_at, status,
|
|
79
|
-
sent_at, delivered_at, read_at, synced_at, retry_count)
|
|
80
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
81
|
-
`),
|
|
82
|
-
|
|
83
|
-
getThread: this.db.prepare(`
|
|
84
|
-
SELECT local_id, server_id, thread_id, from_handle, to_handle, content, created_at,
|
|
85
|
-
status, sent_at, delivered_at, read_at, synced_at, retry_count
|
|
86
|
-
FROM messages
|
|
87
|
-
WHERE (from_handle = ? AND to_handle = ?)
|
|
88
|
-
OR (from_handle = ? AND to_handle = ?)
|
|
89
|
-
ORDER BY created_at ASC
|
|
90
|
-
LIMIT ?
|
|
91
|
-
`),
|
|
92
|
-
|
|
93
|
-
updateStatus: this.db.prepare(`
|
|
94
|
-
UPDATE messages
|
|
95
|
-
SET status = ?, server_id = COALESCE(?, server_id), thread_id = COALESCE(?, thread_id), sent_at = COALESCE(?, sent_at)
|
|
96
|
-
WHERE local_id = ?
|
|
97
|
-
`),
|
|
98
|
-
|
|
99
|
-
getInboxThreads: this.db.prepare(`
|
|
100
|
-
WITH thread_partners AS (
|
|
101
|
-
SELECT DISTINCT
|
|
102
|
-
CASE WHEN from_handle = ? THEN to_handle ELSE from_handle END as partner
|
|
103
|
-
FROM messages
|
|
104
|
-
WHERE from_handle = ? OR to_handle = ?
|
|
105
|
-
),
|
|
106
|
-
latest_messages AS (
|
|
107
|
-
SELECT
|
|
108
|
-
CASE WHEN from_handle = ? THEN to_handle ELSE from_handle END as partner,
|
|
109
|
-
local_id, server_id, from_handle, to_handle, content, created_at,
|
|
110
|
-
status, sent_at, delivered_at, read_at, synced_at, retry_count,
|
|
111
|
-
ROW_NUMBER() OVER (PARTITION BY CASE WHEN from_handle = ? THEN to_handle ELSE from_handle END
|
|
112
|
-
ORDER BY created_at DESC) as rn
|
|
113
|
-
FROM messages
|
|
114
|
-
WHERE from_handle = ? OR to_handle = ?
|
|
115
|
-
),
|
|
116
|
-
unread_counts AS (
|
|
117
|
-
SELECT
|
|
118
|
-
CASE WHEN to_handle = ? THEN from_handle ELSE to_handle END as partner,
|
|
119
|
-
COUNT(*) as unread
|
|
120
|
-
FROM messages
|
|
121
|
-
WHERE to_handle = ? AND status IN ('sent', 'delivered')
|
|
122
|
-
GROUP BY partner
|
|
123
|
-
)
|
|
124
|
-
SELECT
|
|
125
|
-
lm.partner, lm.local_id, lm.server_id, lm.from_handle, lm.to_handle,
|
|
126
|
-
lm.content, lm.created_at, lm.status, lm.sent_at, lm.delivered_at,
|
|
127
|
-
lm.read_at, lm.synced_at, lm.retry_count,
|
|
128
|
-
COALESCE(uc.unread, 0) as unread_count
|
|
129
|
-
FROM latest_messages lm
|
|
130
|
-
LEFT JOIN unread_counts uc ON lm.partner = uc.partner
|
|
131
|
-
WHERE lm.rn = 1
|
|
132
|
-
ORDER BY lm.created_at DESC
|
|
133
|
-
`),
|
|
134
|
-
|
|
135
|
-
markThreadRead: this.db.prepare(`
|
|
136
|
-
UPDATE messages
|
|
137
|
-
SET status = 'read', read_at = ?
|
|
138
|
-
WHERE from_handle = ? AND to_handle = ? AND status IN ('sent', 'delivered')
|
|
139
|
-
`),
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Save a local message (optimistic - before server confirmation)
|
|
145
|
-
*/
|
|
146
|
-
saveLocalMessage(message) {
|
|
147
|
-
const {
|
|
148
|
-
local_id = randomUUID(),
|
|
149
|
-
server_id = null,
|
|
150
|
-
thread_id = null,
|
|
151
|
-
from_handle,
|
|
152
|
-
to_handle,
|
|
153
|
-
content,
|
|
154
|
-
created_at = new Date().toISOString(),
|
|
155
|
-
status = 'pending',
|
|
156
|
-
sent_at = null,
|
|
157
|
-
delivered_at = null,
|
|
158
|
-
read_at = null,
|
|
159
|
-
synced_at = null,
|
|
160
|
-
retry_count = 0
|
|
161
|
-
} = message;
|
|
162
|
-
|
|
163
|
-
this.stmts.insert.run(
|
|
164
|
-
local_id,
|
|
165
|
-
server_id,
|
|
166
|
-
thread_id,
|
|
167
|
-
from_handle,
|
|
168
|
-
to_handle,
|
|
169
|
-
content,
|
|
170
|
-
created_at,
|
|
171
|
-
status,
|
|
172
|
-
sent_at,
|
|
173
|
-
delivered_at,
|
|
174
|
-
read_at,
|
|
175
|
-
synced_at,
|
|
176
|
-
retry_count
|
|
177
|
-
);
|
|
178
|
-
|
|
179
|
-
return local_id;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Get messages for a thread between two users
|
|
184
|
-
*/
|
|
185
|
-
getThreadMessages(handle1, handle2, limit = 100) {
|
|
186
|
-
return this.stmts.getThread.all(handle1, handle2, handle2, handle1, limit).map(row => ({
|
|
187
|
-
local_id: row.local_id,
|
|
188
|
-
server_id: row.server_id,
|
|
189
|
-
thread_id: row.thread_id,
|
|
190
|
-
from_handle: row.from_handle,
|
|
191
|
-
to_handle: row.to_handle,
|
|
192
|
-
content: row.content,
|
|
193
|
-
created_at: row.created_at,
|
|
194
|
-
status: row.status,
|
|
195
|
-
sent_at: row.sent_at,
|
|
196
|
-
delivered_at: row.delivered_at,
|
|
197
|
-
read_at: row.read_at,
|
|
198
|
-
synced_at: row.synced_at,
|
|
199
|
-
retry_count: row.retry_count
|
|
200
|
-
}));
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Update message status after server response
|
|
205
|
-
*/
|
|
206
|
-
updateMessageStatus(local_id, status, server_id = null, thread_id = null) {
|
|
207
|
-
const sent_at = (status === 'sent' || status === 'delivered' || status === 'read')
|
|
208
|
-
? new Date().toISOString()
|
|
209
|
-
: null;
|
|
210
|
-
|
|
211
|
-
this.stmts.updateStatus.run(status, server_id, thread_id, sent_at, local_id);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Merge server messages into local cache (V2 Postgres format)
|
|
216
|
-
* Uses INSERT OR IGNORE to avoid overwriting local changes
|
|
217
|
-
*/
|
|
218
|
-
mergeServerMessages(messages) {
|
|
219
|
-
const insert = this.db.prepare(`
|
|
220
|
-
INSERT OR IGNORE INTO messages
|
|
221
|
-
(local_id, server_id, thread_id, from_handle, to_handle, content, created_at, status,
|
|
222
|
-
sent_at, delivered_at, read_at, synced_at, retry_count)
|
|
223
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
224
|
-
`);
|
|
225
|
-
|
|
226
|
-
const transaction = this.db.transaction((msgs) => {
|
|
227
|
-
for (const msg of msgs) {
|
|
228
|
-
insert.run(
|
|
229
|
-
msg.local_id || msg.id || randomUUID(), // Handle both formats
|
|
230
|
-
msg.server_id || msg.id,
|
|
231
|
-
msg.thread_id, // V2 Postgres thread_id
|
|
232
|
-
msg.from_handle || msg.from,
|
|
233
|
-
msg.to_handle || msg.to,
|
|
234
|
-
msg.content || msg.body || msg.text || '',
|
|
235
|
-
msg.created_at || msg.createdAt || new Date().toISOString(),
|
|
236
|
-
msg.status || 'delivered',
|
|
237
|
-
msg.sent_at || msg.sentAt || msg.created_at || msg.createdAt,
|
|
238
|
-
msg.delivered_at || msg.deliveredAt,
|
|
239
|
-
msg.read_at || msg.readAt,
|
|
240
|
-
new Date().toISOString(), // synced_at
|
|
241
|
-
0 // retry_count
|
|
242
|
-
);
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
transaction(messages);
|
|
247
|
-
return messages.length;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Get inbox threads for a user
|
|
252
|
-
*/
|
|
253
|
-
getInboxThreads(handle) {
|
|
254
|
-
const rows = this.stmts.getInboxThreads.all(
|
|
255
|
-
handle, handle, handle, // thread_partners CTE
|
|
256
|
-
handle, handle, handle, handle, // latest_messages CTE
|
|
257
|
-
handle, handle // unread_counts CTE
|
|
258
|
-
);
|
|
259
|
-
|
|
260
|
-
return rows.map(row => ({
|
|
261
|
-
partner: row.partner,
|
|
262
|
-
latestMessage: {
|
|
263
|
-
local_id: row.local_id,
|
|
264
|
-
server_id: row.server_id,
|
|
265
|
-
from_handle: row.from_handle,
|
|
266
|
-
to_handle: row.to_handle,
|
|
267
|
-
content: row.content,
|
|
268
|
-
created_at: row.created_at,
|
|
269
|
-
status: row.status,
|
|
270
|
-
sent_at: row.sent_at,
|
|
271
|
-
delivered_at: row.delivered_at,
|
|
272
|
-
read_at: row.read_at,
|
|
273
|
-
synced_at: row.synced_at,
|
|
274
|
-
retry_count: row.retry_count
|
|
275
|
-
},
|
|
276
|
-
unreadCount: row.unread_count
|
|
277
|
-
}));
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Mark all messages in a thread as read
|
|
282
|
-
*/
|
|
283
|
-
markThreadRead(my_handle, other_handle) {
|
|
284
|
-
const now = new Date().toISOString();
|
|
285
|
-
const result = this.stmts.markThreadRead.run(now, other_handle, my_handle);
|
|
286
|
-
return result.changes;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Get pending/failed messages for retry
|
|
291
|
-
*/
|
|
292
|
-
getPendingMessages() {
|
|
293
|
-
const rows = this.db.prepare(`
|
|
294
|
-
SELECT local_id, server_id, from_handle, to_handle, content, created_at,
|
|
295
|
-
status, sent_at, delivered_at, read_at, synced_at, retry_count
|
|
296
|
-
FROM messages
|
|
297
|
-
WHERE status = 'pending' OR status = 'failed'
|
|
298
|
-
ORDER BY created_at ASC
|
|
299
|
-
`).all();
|
|
300
|
-
|
|
301
|
-
return rows.map(row => ({
|
|
302
|
-
local_id: row.local_id,
|
|
303
|
-
server_id: row.server_id,
|
|
304
|
-
from_handle: row.from_handle,
|
|
305
|
-
to_handle: row.to_handle,
|
|
306
|
-
content: row.content,
|
|
307
|
-
created_at: row.created_at,
|
|
308
|
-
status: row.status,
|
|
309
|
-
sent_at: row.sent_at,
|
|
310
|
-
delivered_at: row.delivered_at,
|
|
311
|
-
read_at: row.read_at,
|
|
312
|
-
synced_at: row.synced_at,
|
|
313
|
-
retry_count: row.retry_count
|
|
314
|
-
}));
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
close() {
|
|
318
|
-
this.db.close();
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// Export singleton instance
|
|
323
|
-
let instance = null;
|
|
324
|
-
|
|
325
|
-
function getInstance() {
|
|
326
|
-
if (!instance) {
|
|
327
|
-
try {
|
|
328
|
-
instance = new MessageStore();
|
|
329
|
-
} catch (error) {
|
|
330
|
-
console.error('[SQLite] Failed to initialize:', error.message);
|
|
331
|
-
// Return stub with no-op methods if SQLite fails
|
|
332
|
-
return {
|
|
333
|
-
saveLocalMessage: () => randomUUID(),
|
|
334
|
-
getThreadMessages: () => [],
|
|
335
|
-
updateMessageStatus: () => {},
|
|
336
|
-
mergeServerMessages: () => 0,
|
|
337
|
-
getInboxThreads: () => [],
|
|
338
|
-
markThreadRead: () => 0,
|
|
339
|
-
getPendingMessages: () => [],
|
|
340
|
-
close: () => {}
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
return instance;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
module.exports = getInstance();
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|